|
Posted
over 17 years
ago
by
David Grove
Page
edited by
David Grove
- "update class names."
Most Java runtimes rely upon the foreign language APIs of the underlying platform operating system to implement
... [More]
runtime behaviour which involves interaction with the underlying platform. Runtimes also occasionally employ small segments of machine code to provide access to platform hardware state. Note that this is expedient rather than mandatory. With a suitably smart Java bytecode compiler it would be quite possible to implement a full Java-in-Java runtime i.e. one comprising only compiled Java code (the JNode project is an attempt to implement a runtime along these lines; the Xerox, MIT, Lambda and TI Explorer Lisp machine implementations and the Xerox Smalltalk implementation were highly successful attemtps at fully compiled language runtimes).
This section provides information on magic which is an escape hatch that Jikes™ RVM provides to implement functionality that is not possible using the pure Java™ programming language. For example, the Jikes RVM garbage collectors and runtime system must, on occasion, access memory or perform unsafe casts. The compiler will also translate a call to Magic.threadSwitch() into a sequence of machine code that swaps out old thread registers and swaps in new ones, switching execution to the new thread's stack resumed at its saved PC
There are three mechanisms via which the Jikes RVM magic is implemented:
Compiler Intrinsics: Most methods are within class librarys but some functions are built in (that is, intrinsic) to the compiler. These are referred to as intrinsic functions or intrinsics.
[Compiler Pragmas]: Some intrinsics are do not provide any behaviour but instead provide information to the compiler that modifies optimizations, calling conventions and activation frame layout. We rever to these mechanisms as compiler pragmas.
Unboxed Types: Besides the primitive types, all Java values are boxed types. Conceptually, they are represented by a pointer to a heap object. However, an unboxed type is represented by the value itself. All methods on an unboxed type must be Compiler Intrinsics.
The mechanisms are used to implement the following functionality;
Raw Memory Access: Unfetted access to memory.
Uninterruptible Code: Declaring code to be uninterruptible.
[Alternative Calling Conventions]: Declaring different calling conventions and activation frame layout.
View Online
Changes between revision 4
and revision 5:
Most Java runtimes rely upon the foreign language APIs of the underlying platform operating system to implement runtime behaviour which involves interaction with the underlying platform. Runtimes also occasionally employ small segments of machine code to provide access to platform hardware state. Note that this is expedient rather than mandatory. With a suitably smart Java bytecode compiler it would be quite possible to implement a full Java-in-Java runtime i.e. one comprising only compiled Java code (the JNode project is an attempt to implement a runtime along these lines; the Xerox, MIT, Lambda and TI Explorer Lisp machine implementations and the Xerox Smalltalk implementation were highly successful attemtps at fully compiled language runtimes).
This section provides information on (*r) magic (*r) which is an escape hatch that Jikes[™|Trademarks] RVM provides to implement functionality that is not possible using the pure Java[™|Trademarks] programming language. For example, the Jikes RVM garbage collectors and runtime system must, on occasion, access memory or perform unsafe casts. The compiler will also translate a call to VM_Magic.threadSwitch() into a sequence of machine code that swaps out old thread registers and swaps in new ones, switching execution to the new thread's stack resumed at its saved PC
This section provides information on (*r) magic (*r) which is an escape hatch that Jikes[™|Trademarks] RVM provides to implement functionality that is not possible using the pure Java[™|Trademarks] programming language. For example, the Jikes RVM garbage collectors and runtime system must, on occasion, access memory or perform unsafe casts. The compiler will also translate a call to Magic.threadSwitch() into a sequence of machine code that swaps out old thread registers and swaps in new ones, switching execution to the new thread's stack resumed at its saved PC
There are three mechanisms via which the Jikes RVM (*r) magic (*r) is implemented:
* [Compiler Intrinsics]: Most methods are within class librarys but some functions are built in (that is, intrinsic) to the compiler. These are referred to as intrinsic functions or intrinsics.
* [Compiler Pragmas]: Some intrinsics are do not provide any behaviour but instead provide information to the compiler that modifies optimizations, calling conventions and activation frame layout. We rever to these mechanisms as compiler pragmas.
* [Unboxed Types]: Besides the primitive types, all Java values are boxed types. Conceptually, they are represented by a pointer to a heap object. However, an unboxed type is represented by the value itself. All methods on an unboxed type must be [Compiler Intrinsics].
The mechanisms are used to implement the following functionality;
* [Raw Memory Access]: Unfetted access to memory.
* [Uninterruptible Code]: Declaring code to be uninterruptible.
* [Alternative Calling Conventions]: Declaring different calling conventions and activation frame layout.
View All Revisions |
Revert To Version 4
[Less]
|
|
Posted
over 17 years
ago
by
David Grove
Page
edited by
David Grove
- "update class names."
AIX/PowerPC VM Conventions
This section describes register, stack, and calling conventions that apply to
... [More]
Jikes RVM on AIX/PowerPC™.
Stackframe layout and calling conventions may evolve as our understanding of Jikes RVM's performance improves. Where possible, API's should be used to protect code against such changes. In particular, we may move to the AIX™ conventions at a later date. Where code differs from the AIX conventions, it should be marked with a comment to that effect containing the string "AIX".
Register conventions
Registers (general purpose, gp, and floating point, fp) can be roughly categorized into four types:
Scratch: Needed for method prologue/epilogue. Can be used by compiler between calls.
Dedicated: Reserved registers with known contents:
JTOC - Jikes RVM Table Of Contents. Globally accessible data: constants, static fields and methods.
FP - Frame Pointer Current stack frame (thread specific).
PR - Processor register. An object representing the current virtual processor (the one executing on the CPU containing these registers). A field in this object contains a reference to the object representing the RVMThread being executed.
Volatile ("caller save", or "parameter"): Like scratch registers, these can be used by the compiler as temporaries, but they are not preserved across calls. Volatile registers differ from scratch registers in that volatiles can be used to pass parameters and result(s) to and from methods.
Nonvolatile ("callee save", or "preserved"): These can be used (and are preserved across calls), but they must be saved on method entry and restored at method exit. Highest numbered registers are to be used first. (At least initially, nonvolatile registers will not be used to pass parameters.)
Condition Register's 4-bit fields: We follow the AIX conventions to minimize cost in JNI transitions between C and Java code. The baseline compiler only uses CR0. The opt compiler allocates CR0, CR1, CR5 and CR6 and reserves CR7 for use in yieldpoints. None of the compilers use CR2, CR3, or CR4 to avoid saving/restoring condition registers when doing a JNI transition from C to Java code.
CR0, CR1, CR5, CR6, CR7 - volatile
CR2, CR3, CR4 - non-volatile
Stack conventions
Stacks grow from high memory to low memory. The layout of the stackframe appears in a block comment in ppc/StackframeLayoutConstants.java.
Calling Conventions
Parameters
All parameters (that fit) are passed in VOLATILE registers. Object reference and int parameters (or results) consume one GP register; long parameters, two gp registers (low-order half in the first); float and double parameters, one fp register. Parameters are assigned to registers starting with the lowest volatile register through the highest volatile register of the required kind (gp or fp).
Any additional parameters are passed on the stack in a parameter spill area of the caller's stack frame. The first spilled parameter occupies the lowest memory slot. Slots are filled in the order that parameters are spilled.
An int, or object reference, result is returned in the first volatile gp register; a float or double result is returned in the first volatile fp register; a long result is returned in the first two volatile gp registers (low-order half in the first);
Method prologue responsibilities
(some of these can be omitted for leaf methods):
Execute a stackoverflow check, and grow the thread stack if necessary.
Save the caller's next instruction pointer (callee's return address, from the Link Register).
Save any nonvolatile floating-point registers used by callee.
Save any nonvolatile general-purpose registers used by callee.
Store and update the frame pointer FP.
Store callee's compiled method ID
Check to see if the Java™ thread must yield the Processor (and yield if threadswitch was requested).
Method epilogue responsibilities
(some of these can be ommitted for leaf methods):
Restore FP to point to caller's stack frame.
Restore any nonvolatile general-purpose registers used by callee.
Restore any nonvolatile floating-point registers used by callee.
Branch to the return address in caller.
Linux/x86-32 VM Conventions
This section describes register, stack, and calling conventions that apply to Jikes RVM on Linux®/x86-32.
Register conventions
EAX: First GPR parameter register, first GPR result value (high-order part of a long result), otherwise volatile (caller-save).
ECX: Scratch.
EDX: Second GPR parameter register, second GPR result value (low-order part of a long result), otherwise volatile (caller-save).
EBX: Nonvolatile.
ESP: Stack pointer.
EBP: Nonvolatile.
ESI: Processor register, reference to the Processor object for the current virtual processor.
EDI: Nonvolatile. (used to hold JTOC in baseline compiled code)
Stack conventions
Stacks grow from high memory to low memory. The layout of the stackframe appears in a block comment in ia32/StackframeLayoutConstants.java.
Calling Conventions
At the beginning of callee's prologue
The first two areas of the callee's stackframe (see above) have been established. ESP points to caller's return address. Parameters from caller to callee are as mandated by ia32/RegisterConstants.java.
After callee's epilogue
Callee's stackframe has been removed. ESP points to the word above where callee's frame was. The framePointer field of the Processor object pointed to by ESI points to A's frame. If B returns a floating-point result, this is at the top of the fp register stack. If B returns a long, the low-order word is in EAX and the high-order word is in EDX. Otherwise, if B has a result, it is in EAX.
OS X VM Conventions
Calling Conventions
The calling conventions we use for OS X are the same as those listed at:
http://developer.apple.com/documentation/DeveloperTools/Conceptual/MachORuntime/MachORuntime.pdf
They're similar to the Linux PowerPC calling conventions. One major difference is how the two operating systems handle the case of a long parameter when you only have a single parameter register left.
View Online
Changes between revision 3
and revision 4:
h2. AIX/PowerPC VM Conventions
This section describes register, stack, and calling conventions that apply to Jikes RVM on AIX/PowerPC[™|Trademarks].
Stackframe layout and calling conventions may evolve as our understanding of Jikes RVM's performance improves. Where possible, API's should be used to protect code against such changes. In particular, we may move to the AIX[™|Trademarks] conventions at a later date. Where code differs from the AIX conventions, it should be marked with a comment to that effect containing the string "AIX".
Register conventions
h3. Registers (general purpose, gp, and floating point, fp) can be roughly categorized into four types:
* *Scratch:* Needed for method prologue/epilogue. Can be used by compiler between calls.
* *Scratch:* Needed for method prologue/epilogue. Can be used by compiler between calls.
* *Dedicated:* Reserved registers with known contents:
** *JTOC* - Jikes RVM Table Of Contents. Globally accessible data: constants, static fields and methods.
** *FP* - Frame Pointer Current stack frame (thread specific).
** *PR* - Processor register. An object representing the current virtual processor (the one executing on the CPU containing these registers). A field in this object contains a reference to the object representing the VM_Thread being executed.
* *Volatile ("caller save", or "parameter"):* Like scratch registers, these can be used by the compiler as temporaries, but they are not preserved across calls. Volatile registers differ from scratch registers in that volatiles can be used to pass parameters and result(s) to and from methods.
* *Nonvolatile ("callee save", or "preserved"):* These can be used (and are preserved across calls), but they must be saved on method entry and restored at method exit. Highest numbered registers are to be used first. (At least initially, nonvolatile registers will not be used to pass parameters.)
* *Condition Register's 4-bit fields:* We follow the AIX conventions to minimize cost in JNI transitions between C and Java code. The baseline compiler only uses CR0. The opt compiler allocates CR0, CR1, CR5 and CR6 and reserves CR7 for use in yieldpoints. None of the compilers use CR2, CR3, or CR4 to avoid saving/restoring condition registers when doing a JNI transition from C to Java code.
** *CR0, CR1, CR5, CR6, CR7* - volatile
** *CR2, CR3, CR4* - non-volatile
** *JTOC* \- Jikes RVM Table Of Contents. Globally accessible data: constants, static fields and methods.
** *FP* \- Frame Pointer Current stack frame (thread specific).
** *PR* \- Processor register. An object representing the current virtual processor (the one executing on the CPU containing these registers). A field in this object contains a reference to the object representing the RVMThread being executed.
* *Volatile ("caller save", or "parameter"):* Like scratch registers, these can be used by the compiler as temporaries, but they are not preserved across calls. Volatile registers differ from scratch registers in that volatiles can be used to pass parameters and result(s) to and from methods.
* *Nonvolatile ("callee save", or "preserved"):* These can be used (and are preserved across calls), but they must be saved on method entry and restored at method exit. Highest numbered registers are to be used first. (At least initially, nonvolatile registers will not be used to pass parameters.)
* *Condition Register's 4-bit fields:* We follow the AIX conventions to minimize cost in JNI transitions between C and Java code. The baseline compiler only uses CR0. The opt compiler allocates CR0, CR1, CR5 and CR6 and reserves CR7 for use in yieldpoints. None of the compilers use CR2, CR3, or CR4 to avoid saving/restoring condition registers when doing a JNI transition from C to Java code.
** *CR0, CR1, CR5, CR6, CR7* \- volatile
** *CR2, CR3, CR4* \- non-volatile
h3. Stack conventions
Stacks grow from high memory to low memory. The layout of the stackframe appears in a block comment in {{ppc/VM_StackframeLayoutConstants.java}}.
Stacks grow from high memory to low memory. The layout of the stackframe appears in a block comment in {{ppc/StackframeLayoutConstants.java}}.
h3. Calling Conventions
h4. Parameters
All parameters (that fit) are passed in VOLATILE registers. Object reference and int parameters (or results) consume one GP register; long parameters, two gp registers (low-order half in the first); float and double parameters, one fp register. Parameters are assigned to registers starting with the lowest volatile register through the highest volatile register of the required kind (gp or fp).
Any additional parameters are passed on the stack in a parameter spill area of the caller's stack frame. The first spilled parameter occupies the lowest memory slot. Slots are filled in the order that parameters are spilled.
An int, or object reference, result is returned in the first volatile gp register; a float or double result is returned in the first volatile fp register; a long result is returned in the first two volatile gp registers (low-order half in the first);
An int, or object reference, result is returned in the first volatile gp register; a float or double result is returned in the first volatile fp register; a long result is returned in the first two volatile gp registers (low-order half in the first);
h4. Method prologue responsibilities
(some of these can be omitted for leaf methods):
# Execute a stackoverflow check, and grow the thread stack if necessary.
# Save the caller's next instruction pointer (callee's return address, from the Link Register).
# Save any nonvolatile floating-point registers used by callee.
# Save any nonvolatile general-purpose registers used by callee.
# Store and update the frame pointer FP.
# Store callee's compiled method ID
# Check to see if the Java[™|Trademarks] thread must yield the VM_Processor (and yield if threadswitch was requested).
# Check to see if the Java[™|Trademarks] thread must yield the Processor (and yield if threadswitch was requested).
h4. Method epilogue responsibilities
(some of these can be ommitted for leaf methods):
# Restore FP to point to caller's stack frame.
# Restore any nonvolatile general-purpose registers used by callee.
# Restore any nonvolatile floating-point registers used by callee.
# Branch to the return address in caller.
# Branch to the return address in caller.
h2. Linux/x86-32 VM Conventions
This section describes register, stack, and calling conventions that apply to Jikes RVM on Linux[®|Trademarks]/x86-32.
h3. Register conventions
* *EAX:* First GPR parameter register, first GPR result value (high-order part of a long result), otherwise volatile (caller-save).
* *ECX:* Scratch.
* *EDX:* Second GPR parameter register, second GPR result value (low-order part of a long result), otherwise volatile (caller-save).
* *EBX:* Nonvolatile.
* *ESP:* Stack pointer.
* *EBP:* Nonvolatile.
* *ESI:* Processor register, reference to the VM_Processor object for the current virtual processor.
* *EDI:* Nonvolatile. (used to hold JTOC in baseline compiled code)
* *EAX:* First GPR parameter register, first GPR result value (high-order part of a long result), otherwise volatile (caller-save).
* *ECX:* Scratch.
* *EDX:* Second GPR parameter register, second GPR result value (low-order part of a long result), otherwise volatile (caller-save).
* *EBX:* Nonvolatile.
* *ESP:* Stack pointer.
* *EBP:* Nonvolatile.
* *ESI:* Processor register, reference to the Processor object for the current virtual processor.
* *EDI:* Nonvolatile. (used to hold JTOC in baseline compiled code)
h3. Stack conventions
Stacks grow from high memory to low memory. The layout of the stackframe appears in a block comment in {{ia32/VM_StackframeLayoutConstants.java}}.
Stacks grow from high memory to low memory. The layout of the stackframe appears in a block comment in {{ia32/StackframeLayoutConstants.java}}.
h3. Calling Conventions
h4. At the beginning of callee's prologue
The first two areas of the callee's stackframe (see above) have been established. ESP points to caller's return address. Parameters from caller to callee are as mandated by {{ia32/VM_RegisterConstants.java}}.
The first two areas of the callee's stackframe (see above) have been established. ESP points to caller's return address. Parameters from caller to callee are as mandated by {{ia32/RegisterConstants.java}}.
h4. After callee's epilogue
Callee's stackframe has been removed. ESP points to the word above where callee's frame was. The framePointer field of the VM_Processor object pointed to by ESI points to A's frame. If B returns a floating-point result, this is at the top of the fp register stack. If B returns a long, the low-order word is in EAX and the high-order word is in EDX. Otherwise, if B has a result, it is in EAX.
Callee's stackframe has been removed. ESP points to the word above where callee's frame was. The framePointer field of the Processor object pointed to by ESI points to A's frame. If B returns a floating-point result, this is at the top of the fp register stack. If B returns a long, the low-order word is in EAX and the high-order word is in EDX. Otherwise, if B has a result, it is in EAX.
h2. OS X VM Conventions
h3. Calling Conventions
The calling conventions we use for OS X are the same as those listed at:
[http://developer.apple.com/documentation/DeveloperTools/Conceptual/MachORuntime/MachORuntime.pdf]
They're similar to the Linux PowerPC calling conventions. One major difference is how the two operating systems handle the case of a long parameter when you only have a single parameter register left.
View All Revisions |
Revert To Version 3
[Less]
|
|
Posted
over 17 years
ago
by
David Grove
Page
edited by
David Grove
- "update class names."
Jikes™ RVM provides callbacks for many runtime events of interest to the Jikes RVM programmer, such as
... [More]
classloading, VM boot image creation, and VM exit. The callbacks allow arbitrary code to be executed on any of the supported events.
The callbacks are accessed through the nested interfaces defined in the Callbacks class. There is one interface per event type. To be notified of an event, register an instance of a class that implements the corresponding interface with Callbacks by calling the corresponding add...() method. For example, to be notified when a class is instantiated, first implement the Callbacks.ClassInstantiatedMonitor interface, and then call Callbacks.addClassInstantiatedMonitor() with an instance of your class. When any class is instantiated, the notifyClassInstantiated method in your instance will be invoked.
The appropriate interface names can be obtained by appending "Monitor" to the event names (e.g. the interface to implement for the MethodOverride event is Callbacks.MethodOverrideMonitor). Likewise, the method to register the callback is "add", followed by the name of the interface (e.g. the register method for the above interface is Callbacks.addMethodOverrideMonitor()).
Since the events for which callbacks are available are internal to the VM, there are limitations on the behavior of the callback code. For example, as soon as the exit callback is invoked, all threads are considered daemon threads (i.e. the VM will not wait for any new threads created in the callbacks to complete before exiting). Thus, if the exit callback creates any threads, it has to join() with them before returning. These limitations may also produce some unexpected behavior. For example, while there is an elementary safeguard on any classloading callback that prevents recursive invocation (i.e. if the callback code itself causes classloading), there is no such safeguard across events, so, if there are callbacks registered for both ClassLoaded and ClassInstantiated events, and the ClassInstantiated callback code causes dynamic class loading, the ClassLoaded callback will be invoked for the new class, but not the ClassInstantiated callback.
Examples of callback use can be seen in the Controller class in the adaptive system and the GCStatistics class.
An Example: Modifying SPECjvm98 to Report the End of a Run
The SPECjvm®98 benchmark suite is configured to run one or more benchmarks a particular number of times. For example, the following runs the compress benchmark for 5 iterations:
rvm SpecApplication -m5 -M5 -s100 -a _201_compress
It is sometimes useful to have the VM notified when the application has completed an iteration of the benchmark. This can be performed by using the Callbacks interface. The specifics are specified below:
Modify spec/harness/ProgramRunner.java:
add an import statement for the Callbacks class:
import com.ibm.jikesrvm.Callbacks;
before the call to runOnce add the following:
Callbacks.notifyAppRunStart(className, run);
after the call to runOnce add the following:
Callbacks.notifyAppRunComplete(className, run);
Recompile the modified file:
javac -classpath .:$RVM_BUILD/RVM.classes:$RVM_BUILD/RVM.classes/rvmrt.jar spec/harness/ProgramRunner.java
or create a stub version of Callbacks.java and place it the appropriate directory structure with your modified file, i.e., com/ibm/jikesrvm/Callbacks.java
Run Jikes RVM as you normally would using the SPECjvm98 benchmarks.
In the current system the Controller class will gain control when these callbacks are made and print a message into the AOS log file (by default, placed in Jikes RVM's current working directory and called AOSLog.txt).
Another Example: Directing a Recompilation of All Methods During the Application's Execution
Another callback of interest allows an application to direct the VM to recompile all executed methods at a certain point of the application's execution by calling the recompileAllDynamicallyLoadedMethods method in the Callbacks class. This functionality can be useful to experiment with the performance effects of when compilation occurs. This VM functionality can be disabled using the DISABLE_RECOMPILE_ALL_METHODS boolean flag to the adaptive system.
View Online
Changes between revision 3
and revision 4:
Jikes[™|Trademarks] RVM provides callbacks for many runtime events of interest to the Jikes RVM programmer, such as classloading, VM boot image creation, and VM exit. The callbacks allow arbitrary code to be executed on any of the supported events.
The callbacks are accessed through the nested interfaces defined in the VM_Callbacks class. There is one interface per event type. To be notified of an event, register an instance of a class that implements the corresponding interface with VM_Callbacks by calling the corresponding add...() method. For example, to be notified when a class is instantiated, first implement the VM_Callbacks.ClassInstantiatedMonitor interface, and then call VM_Callbacks.addClassInstantiatedMonitor() with an instance of your class. When any class is instantiated, the notifyClassInstantiated method in your instance will be invoked.
The callbacks are accessed through the nested interfaces defined in the Callbacks class. There is one interface per event type. To be notified of an event, register an instance of a class that implements the corresponding interface with Callbacks by calling the corresponding add...() method. For example, to be notified when a class is instantiated, first implement the Callbacks.ClassInstantiatedMonitor interface, and then call Callbacks.addClassInstantiatedMonitor() with an instance of your class. When any class is instantiated, the notifyClassInstantiated method in your instance will be invoked.
The appropriate interface names can be obtained by appending "Monitor" to the event names (e.g. the interface to implement for the MethodOverride event is VM_Callbacks.MethodOverrideMonitor). Likewise, the method to register the callback is "add", followed by the name of the interface (e.g. the register method for the above interface is VM_Callbacks.addMethodOverrideMonitor()).
The appropriate interface names can be obtained by appending "Monitor" to the event names (e.g. the interface to implement for the MethodOverride event is Callbacks.MethodOverrideMonitor). Likewise, the method to register the callback is "add", followed by the name of the interface (e.g. the register method for the above interface is Callbacks.addMethodOverrideMonitor()).
Since the events for which callbacks are available are internal to the VM, there are limitations on the behavior of the callback code. For example, as soon as the exit callback is invoked, all threads are considered daemon threads (i.e. the VM will not wait for any new threads created in the callbacks to complete before exiting). Thus, if the exit callback creates any threads, it has to join() with them before returning. These limitations may also produce some unexpected behavior. For example, while there is an elementary safeguard on any classloading callback that prevents recursive invocation (i.e. if the callback code itself causes classloading), there is no such safeguard across events, so, if there are callbacks registered for both ClassLoaded and ClassInstantiated events, and the ClassInstantiated callback code causes dynamic class loading, the ClassLoaded callback will be invoked for the new class, but not the ClassInstantiated callback.
Examples of callback use can be seen in the VM_Controller class in the adaptive system and the VM_GCStatistics class.
Examples of callback use can be seen in the Controller class in the adaptive system and the GCStatistics class.
h2. An Example: Modifying SPECjvm98 to Report the End of a Run
The SPECjvm[®|Trademarks]98 benchmark suite is configured to run one or more benchmarks a particular number of times. For example, the following runs the compress benchmark for 5 iterations:
rvm SpecApplication -m5 -M5 -s100 -a _201_compress
rvm SpecApplication \-m5 \-M5 \-s100 \-a \_201_compress
It is sometimes useful to have the VM notified when the application has completed an iteration of the benchmark. This can be performed by using the VM_Callbacks interface. The specifics are specified below:
It is sometimes useful to have the VM notified when the application has completed an iteration of the benchmark. This can be performed by using the Callbacks interface. The specifics are specified below:
# Modify spec/harness/ProgramRunner.java:
## add an import statement for the VM_Callbacks class:
## add an import statement for the Callbacks class:
{panel}
import com.ibm.jikesrvm.VM_Callbacks;
import com.ibm.jikesrvm.Callbacks;
{panel}
## before the call to runOnce add the following:
{panel}
VM_Callbacks.notifyAppRunStart(className, run);
Callbacks.notifyAppRunStart(className, run);
{panel}
## after the call to runOnce add the following:
{panel}
VM_Callbacks.notifyAppRunComplete(className, run);
Callbacks.notifyAppRunComplete(className, run);
{panel}
# Recompile the modified file:
{panel}
javac -classpath .:$RVM_BUILD/RVM.classes:$RVM_BUILD/RVM.classes/rvmrt.jar spec/harness/ProgramRunner.java
javac \-classpath .:$RVM_BUILD/RVM.classes:$RVM_BUILD/RVM.classes/rvmrt.jar spec/harness/ProgramRunner.java
{panel}
or create a stub version of VM_Callbacks.java and place it the appropriate directory structure with your modified file, i.e., com/ibm/jikesrvm/VM_Callbacks.java
# Run Jikes RVM as you normally would using the SPECjvm98 benchmarks.
or create a stub version of Callbacks.java and place it the appropriate directory structure with your modified file, i.e., com/ibm/jikesrvm/Callbacks.java
# Run Jikes RVM as you normally would using the SPECjvm98 benchmarks.
In the current system the VM_Controller class will gain control when these callbacks are made and print a message into the AOS log file (by default, placed in Jikes RVM's current working directory and called AOSLog.txt).
In the current system the Controller class will gain control when these callbacks are made and print a message into the AOS log file (by default, placed in Jikes RVM's current working directory and called AOSLog.txt).
h2. Another Example: Directing a Recompilation of All Methods During the Application's Execution
Another callback of interest allows an application to direct the VM to recompile all executed methods at a certain point of the application's execution by calling the recompileAllDynamicallyLoadedMethods method in the VM_Callbacks class. This functionality can be useful to experiment with the performance effects of when compilation occurs. This VM functionality can be disabled using the DISABLE_RECOMPILE_ALL_METHODS boolean flag to the adaptive system.
Another callback of interest allows an application to direct the VM to recompile all executed methods at a certain point of the application's execution by calling the recompileAllDynamicallyLoadedMethods method in the Callbacks class. This functionality can be useful to experiment with the performance effects of when compilation occurs. This VM functionality can be disabled using the DISABLE_RECOMPILE_ALL_METHODS boolean flag to the adaptive system.
View All Revisions |
Revert To Version 3
[Less]
|
|
Posted
over 17 years
ago
by
David Grove
Page
edited by
David Grove
- "update class names."
This section provides some explanation of how Java™ threads are scheduled and synchronized by Jikes™ RVM.
... [More]
All Java threads (application threads, garbage collector threads, etc.) derive from RVMThread. These threads are multiplexed onto one or more virtual processors (see Processor). The number of Jikes RVM virtual processors to use is a command line argument (e.g. -X:processors=4). If no command line argument is given, Jikes RVM will default to creating only one virtual processor. If you want Jikes RVM to utilize more than 1 CPU, then you need to tell it to use the appropriate number of virtual processors. Multiple virtual processors require a working pthread library, each virtual processor being bound to a pthread.
The Jikes RVM is a a M:N thread model, scheduling execution of an arbitrarily large number (M) of Java threads over a finite number (N) of Processors. This means that at most N Java threads can be executing concurrently (true concurrency requires that the underlying platform incorporate multiple execution contexts capable of running several pthreads simultaneously). For maximal performance, you should tell Jikes RVM to create one virtual processor for each CPU on an SMP.
A benefit of M:N threading is that the Java system can only obtain N pthreads worth of scheduled execution time from the underlying operating system. By contrast a 1:1 model would allow the Java system to swamp the system with runnable threads, crowding out system threads and other Unix applications (Of course, in certain situations 1:1 threading may be more appropriate). Another benefit is that system-wide thread management within th RVM involves synchronizing at most N active threads (N is an RVM-boot time constant) rather than an unbounded number of threads. For example, a stop the world garbage collector merely needs to flag the N currently active threads that they should switch into a collector thread rather than having to stop every mutator thread in the system.
A downside to the M:N threading model is that a given Java thread may be scheduled for execution by different pthreads at different stages during in its execution. In particular, when the Java thread calls native methods which rely upon per-pthread storage this choice of threading model is disastrous. Unfortunately many threaded library implementations do exactly this.
Thread Queues
Threads that are not executing are either placed on thread queues (deriving from AbstractThreadQueue) or are proxied (see below). Thread queues are either global or (virtual) processor local. The latter do not require synchronized access but global queues do. Unfortunately, we did not see how to use Java monitors to provide this synchronization. (In part, because it is needed to implement monitors, see below.) Instead this low-level synchronization is provided by ProcessorLocks.
Transferring execution from one thread (A) to another (B) is a complex operation negotiated by the yield and morph methods of RVMThread and the dispatch method of Processor. yield places A on an indicated queue (releasing the lock on the queue, if it is global). morph does some additional housekeeping and transfers control to dispatch which selects the next thread to execute. Dispatch then invokes Magic.threadSwitch to save the hardware context of A and restore the hardware context of B. It now appears as if B's previous call to dispatch has just returned and it continues executing. While dispatching is proceeding (from the time A is enqueued until B's hardware context is restored), the beingDispatched field of A is set to prevent it from being scheduled for execution on some other virtual processor while it is still executing in morph or dispatch.
Jikes RVM has a simple load balancing mechanism. Every once in a while, a thread will move from one virtual processor to the next. Such movement happens when a thread is interrupted by a timer tick (or garbage collection) or when it comes off a global queue (such as, the queues waiting for a heavy-weight lock, see Lock). Such migration will be inhibited if the thread is the last (non-idle) executable thread on its current virtual processor.
If a virtual processor has no other executable thread, its idle thread runs. This thread posts a request for work and then busy-waits for a short time (currently 0.001 seconds). If no work arrives in that period, the virtual processor surrenders the rest of its time slice back to the operating system. If another virtual processor notices that this one needs work, it will tranfer an extra runnable thread (if it has one) to this processor. When work arrives, the idle thread yields to an idle queue, and the recently transferred thread begins execution.
Currently, Jikes RVM has no priority mechanism, that is, all threads run at the same priority.
Synchronization
Jikes RVM uses a light-weight locking scheme to implement Java monitors (see Lock and ThinLock). The exact details of the locking scheme are dependent on which variant of JavaHeader.java is selected at system build time. If an object instance has a light weight lock, then some bits in the object header are used for locking. If the top bit is set, the remainder of the bits are an index into an array of heavy-weight locks. Otherwise, if the object is locked, these bits contain the id of the thread that holds the lock and a count of how many times it is held. If a thread tries to lock an object locked with a light-weight lock by another thread, it can spin, yield, or inflate the lock. Spinning is probably a bad idea. The number of times to yield before inflating is a matter open for investigation (as are a number of locking issues, see Lock). Heavy-weight locks contain an enteringQueue for threads trying to acquire the lock.
A similar mechanism is used to implement Java wait/notification semantics. Heavy-weight locks contain a waitingQueue for threads blocked at a Java wait. When a notify is received, a thread is taken from this queue and transferred to a ready queue. Priority wakeupQueues are used to implement Java sleep semantics. Logically, Java timed-wait semantics entail placing a thread on both a waitingQueue and a wakeupQueue. However, our implementation only allows a thread to be on one thread queue at a time. To accommodate timed-waits, both waitingQueues and wakeupQueues are queues of proxies rather than threads. A Proxy can represent the same thread on more than one proxy queue.
IO Management
Many native IO operations are blocking operations and the thread invoking the operation will block until the IO operation completes. This causes problems for the Jikes RVM M:N thread model. If a RVMThread invokes such an operation the whole Processor will be blocked and unable to schedule any other RVMThreads.
The Jikes RVM attempts to avoid this problem by intercepting blocking IO operations and replacing them with non-blocking operations. The calling thread is then suspended and placed in a ThreadIOQueue. The Processor periodically checks pending IO operations and after they complete the calling thread is moved from the ThreadIOQueue back into the running queue. The Jikes RVM may not always be able to intercept blocking IO operations in native code and can not insert yield points in native code. As a result a long running or blocked native method can block other threads.
View Online
Changes between revision 8
and revision 9:
This section provides some explanation of how Java[™|Trademarks] threads are scheduled and synchronized by Jikes[™|Trademarks] RVM.
All Java threads (application threads, garbage collector threads, etc.) derive from {{VM\_Thread}}. These threads are multiplexed onto one or more virtual processors (see {{VM\_Processor}}). The number of Jikes RVM virtual processors to use is a command line argument (e.g. -X:processors=4). If no command line argument is given, Jikes RVM will default to creating only one virtual processor. If you want Jikes RVM to utilize more than 1 CPU, then you need to tell it to use the appropriate number of virtual processors. Multiple virtual processors require a working pthread library, each virtual processor being bound to a pthread.
All Java threads (application threads, garbage collector threads, etc.) derive from {{RVMThread}}. These threads are multiplexed onto one or more virtual processors (see {{Processor}}). The number of Jikes RVM virtual processors to use is a command line argument (e.g. \-X:processors=4). If no command line argument is given, Jikes RVM will default to creating only one virtual processor. If you want Jikes RVM to utilize more than 1 CPU, then you need to tell it to use the appropriate number of virtual processors. Multiple virtual processors require a working pthread library, each virtual processor being bound to a pthread.
The Jikes RVM is a a M:N thread model, scheduling execution of an arbitrarily large number (M) of Java threads over a finite number (N) of {{VM\_Processors}}. This means that at most N Java threads can be executing concurrently (true concurrency requires that the underlying platform incorporate multiple execution contexts capable of running several pthreads simultaneously). For maximal performance, you should tell Jikes RVM to create one virtual processor for each CPU on an SMP.
The Jikes RVM is a a M:N thread model, scheduling execution of an arbitrarily large number (M) of Java threads over a finite number (N) of {{Processors}}. This means that at most N Java threads can be executing concurrently (true concurrency requires that the underlying platform incorporate multiple execution contexts capable of running several pthreads simultaneously). For maximal performance, you should tell Jikes RVM to create one virtual processor for each CPU on an SMP.
A benefit of M:N threading is that the Java system can only obtain N pthreads worth of scheduled execution time from the underlying operating system. By contrast a 1:1 model would allow the Java system to swamp the system with runnable threads, crowding out system threads and other Unix applications (Of course, in certain situations 1:1 threading may be more appropriate). Another benefit is that system-wide thread management within th RVM involves synchronizing at most N active threads (N is an RVM-boot time constant) rather than an unbounded number of threads. For example, a stop the world garbage collector merely needs to flag the N currently active threads that they should switch into a collector thread rather than having to stop every mutator thread in the system.
A downside to the M:N threading model is that a given Java thread may be scheduled for execution by different pthreads at different stages during in its execution. In particular, when the Java thread calls native methods which rely upon per-pthread storage this choice of threading model is disastrous. Unfortunately many threaded library implementations do exactly this.
h2. Thread Queues
Threads that are not executing are either placed on thread queues (deriving from {{VM\_AbstractThreadQueue}}) or are proxied (see below). Thread queues are either global or (virtual) processor local. The latter do not require synchronized access but global queues do. Unfortunately, we did not see how to use Java monitors to provide this synchronization. (In part, because it is needed to implement monitors, see below.) Instead this low-level synchronization is provided by {{VM\_ProcessorLocks}}.
Threads that are not executing are either placed on thread queues (deriving from {{AbstractThreadQueue}}) or are proxied (see below). Thread queues are either global or (virtual) processor local. The latter do not require synchronized access but global queues do. Unfortunately, we did not see how to use Java monitors to provide this synchronization. (In part, because it is needed to implement monitors, see below.) Instead this low-level synchronization is provided by {{ProcessorLocks}}.
Transferring execution from one thread (A) to another (B) is a complex operation negotiated by the yield and morph methods of {{VM\_Thread}} and the dispatch method of {{VM\_Processor}}. {{yield}} places A on an indicated queue (releasing the lock on the queue, if it is global). {{morph}} does some additional housekeeping and transfers control to dispatch which selects the next thread to execute. Dispatch then invokes {{VM\_Magic.threadSwitch}} to save the hardware context of A and restore the hardware context of B. It now appears as if B's previous call to dispatch has just returned and it continues executing. While dispatching is proceeding (from the time A is enqueued until B's hardware context is restored), the beingDispatched field of A is set to prevent it from being scheduled for execution on some other virtual processor while it is still executing in morph or dispatch.
Transferring execution from one thread (A) to another (B) is a complex operation negotiated by the yield and morph methods of {{RVMThread}} and the dispatch method of {{Processor}}. {{yield}} places A on an indicated queue (releasing the lock on the queue, if it is global). {{morph}} does some additional housekeeping and transfers control to dispatch which selects the next thread to execute. Dispatch then invokes {{Magic.threadSwitch}} to save the hardware context of A and restore the hardware context of B. It now appears as if B's previous call to dispatch has just returned and it continues executing. While dispatching is proceeding (from the time A is enqueued until B's hardware context is restored), the beingDispatched field of A is set to prevent it from being scheduled for execution on some other virtual processor while it is still executing in morph or dispatch.
Jikes RVM has a simple load balancing mechanism. Every once in a while, a thread will move from one virtual processor to the next. Such movement happens when a thread is interrupted by a timer tick (or garbage collection) or when it comes off a global queue (such as, the queues waiting for a heavy-weight lock, see {{VM\_Lock}}). Such migration will be inhibited if the thread is the last (non-idle) executable thread on its current virtual processor.
Jikes RVM has a simple load balancing mechanism. Every once in a while, a thread will move from one virtual processor to the next. Such movement happens when a thread is interrupted by a timer tick (or garbage collection) or when it comes off a global queue (such as, the queues waiting for a heavy-weight lock, see {{Lock}}). Such migration will be inhibited if the thread is the last (non-idle) executable thread on its current virtual processor.
If a virtual processor has no other executable thread, its idle thread runs. This thread posts a request for work and then busy-waits for a short time (currently 0.001 seconds). If no work arrives in that period, the virtual processor surrenders the rest of its time slice back to the operating system. If another virtual processor notices that this one needs work, it will tranfer an extra runnable thread (if it has one) to this processor. When work arrives, the idle thread yields to an idle queue, and the recently transferred thread begins execution.
Currently, Jikes RVM has no priority mechanism, that is, all threads run at the same priority.
h2. Synchronization
Jikes RVM uses a light-weight locking scheme to implement Java monitors (see {{VM\_Lock}} and {{VM\_ThinLock}}). The exact details of the locking scheme are dependent on which variant of VM_JavaHeader.java is selected at system build time. If an object instance has a light weight lock, then some bits in the object header are used for locking. If the top bit is set, the remainder of the bits are an index into an array of heavy-weight locks. Otherwise, if the object is locked, these bits contain the id of the thread that holds the lock and a count of how many times it is held. If a thread tries to lock an object locked with a light-weight lock by another thread, it can spin, yield, or inflate the lock. Spinning is probably a bad idea. The number of times to yield before inflating is a matter open for investigation (as are a number of locking issues, see {{VM\_Lock}}). Heavy-weight locks contain an enteringQueue for threads trying to acquire the lock.
Jikes RVM uses a light-weight locking scheme to implement Java monitors (see {{Lock}} and {{ThinLock}}). The exact details of the locking scheme are dependent on which variant of JavaHeader.java is selected at system build time. If an object instance has a light weight lock, then some bits in the object header are used for locking. If the top bit is set, the remainder of the bits are an index into an array of heavy-weight locks. Otherwise, if the object is locked, these bits contain the id of the thread that holds the lock and a count of how many times it is held. If a thread tries to lock an object locked with a light-weight lock by another thread, it can spin, yield, or inflate the lock. Spinning is probably a bad idea. The number of times to yield before inflating is a matter open for investigation (as are a number of locking issues, see {{Lock}}). Heavy-weight locks contain an enteringQueue for threads trying to acquire the lock.
A similar mechanism is used to implement Java wait/notification semantics. Heavy-weight locks contain a waitingQueue for threads blocked at a Java wait. When a notify is received, a thread is taken from this queue and transferred to a ready queue. Priority wakeupQueues are used to implement Java sleep semantics. Logically, Java timed-wait semantics entail placing a thread on both a waitingQueue and a wakeupQueue. However, our implementation only allows a thread to be on one thread queue at a time. To accommodate timed-waits, both waitingQueues and wakeupQueues are queues of proxies rather than threads. A {{VM\_Proxy}} can represent the same thread on more than one proxy queue.
A similar mechanism is used to implement Java wait/notification semantics. Heavy-weight locks contain a waitingQueue for threads blocked at a Java wait. When a notify is received, a thread is taken from this queue and transferred to a ready queue. Priority wakeupQueues are used to implement Java sleep semantics. Logically, Java timed-wait semantics entail placing a thread on both a waitingQueue and a wakeupQueue. However, our implementation only allows a thread to be on one thread queue at a time. To accommodate timed-waits, both waitingQueues and wakeupQueues are queues of proxies rather than threads. A {{Proxy}} can represent the same thread on more than one proxy queue.
h2. IO Management
Many native IO operations are blocking operations and the thread invoking the operation will block until the IO operation completes. This causes problems for the Jikes RVM M:N thread model. If a {{VM\_Thread}} invokes such an operation the whole {{VM\_Processor}} will be blocked and unable to schedule any other {{VM\_Threads}}.
Many native IO operations are blocking operations and the thread invoking the operation will block until the IO operation completes. This causes problems for the Jikes RVM M:N thread model. If a {{RVMThread}} invokes such an operation the whole {{Processor}} will be blocked and unable to schedule any other {{RVMThreads}}.
The Jikes RVM attempts to avoid this problem by intercepting blocking IO operations and replacing them with non-blocking operations. The calling thread is then suspended and placed in a {{VM\_ThreadIOQueue}}. The {{VM\_Processor}} periodically checks pending IO operations and after they complete the calling thread is moved from the {{VM\_ThreadIOQueue}} back into the running queue. The Jikes RVM may not always be able to intercept blocking IO operations in native code and can not insert yield points in native code. As a result a long running or blocked native method can block other threads.
The Jikes RVM attempts to avoid this problem by intercepting blocking IO operations and replacing them with non-blocking operations. The calling thread is then suspended and placed in a {{ThreadIOQueue}}. The {{Processor}} periodically checks pending IO operations and after they complete the calling thread is moved from the {{ThreadIOQueue}} back into the running queue. The Jikes RVM may not always be able to intercept blocking IO operations in native code and can not insert yield points in native code. As a result a long running or blocked native method can block other threads.
View All Revisions |
Revert To Version 8
[Less]
|
|
Posted
over 17 years
ago
by
David Grove
Page
edited by
David Grove
- "update class names."
Object Model
An object model dictates how to represent objects in storage; the best object model will
... [More]
maximize efficiency of frequent language operations while minimizing storage overhead. Jikes RVM's object model is defined by ObjectModel.
Overview
Values in the Java™ programming language are either primitive (e.g. int, double, etc.) or they are references (that is, pointers) to objects. Objects are either arrays having elements or scalar objectshaving fields. Objects are logically composed of two primary sections: an object header (described in more detail below) and the object's instance fields (or array elements).
The following non-functional requirements govern the Jikes RVM object model:
instance field and array accesses should be as fast as possible,
null-pointer checks should be performed by the hardware if possible,
method dispatch and other frequent runtime services should be fast,
other (less frequent) Java operations should not be prohibitively slow, and
per-object storage overhead (ie object header size) should be as small as possible.
Assuming the reference to an object resides in a register, compiled code can access the object's fields at a fixed displacement in a single instruction. To facilitate array access, the reference to an array points to the first (zeroth) element of an array and the remaining elements are laid out in ascending order. The number of elements in an array, its length, resides just before its first element. Thus, compiled code can access array elements via base scaled index addressing.
The Java programming language requires that an attempt to access an object through a null object reference generates a NullPointerException. In Jikes RVM, references are machine addresses, and null is represented by address 0. On Linux, accesses to both very low and very high memory can be trapped by the hardware, thus all null checks can be made implicit. However, the AIX™ operating system permits loads from low memory, but accesses to very high memory (at small negative offsets from a null pointer) normally cause hardware interrupts. Therefore on AIX only a subset of pointer dereferences can be protected by an implicit null check.
Object Header
Logically, every object header contains the following components:
TIB Pointer: The TIB (Type Information Block) holds information that applies to all objects of a type. The structure of the TIB is defined by TIBLayoutConstants. A TIB includes the virtual method table, a pointer to an object representing the type, and pointers to a few data structures to facilitate efficient interface invocation and dynamic type checking.
Hash Code: Each Java object has an identity hash code. This can be read by Object.hashCode or in the case that this method overridden, by System.identityHashCode. The default hash code is usually the location in memory of the object, however, with some garbage collectors objects can move. So the hash code remains the same, space in the object header may be used to hold the original hash code value.
Lock: Each Java object has an associated lock state. This could be a pointer to a lock object or a direct representation of the lock.
Array Length: Every array object provides a length field that contains the length (number of elements) of the array.
Garbage Collection Information: Each Java object has associated information used by the memory management system. Usually this consists of one or two mark bits, but this could also include some combination of a reference count, forwarding pointer, etc.
Misc Fields: In experimental configurations, the object header can be expanded to add additional fields to every object, typically to support profiling.
An implementation of this abstract header is defined by three files: JavaHeader, which supports TIB access, default hash codes, and locking; AllocatorHeader, which supports garbage collection information; and MiscHeader, which supports adding additional fields to all objects.
Field Layout
Fields tend to be recorded in the Java class file in the order they are declared in the Java source file. We lay out fields in the order they are declared with some exceptions to improve alignment and pack the fields in the object.
Double and long fields benefit from being 8 byte aligned. Every RVMClass records the preferred alignment of the object as a whole. We lay out double and long fields first (and object references if these are 8 bytes long) so that we can avoid making holes in the field layout for alignment. We don't do this for smaller fields as all objects need to be a multiple of 4bytes in size.
When we lay out fields we may create holes to improve alignment. For example, an int following a byte, we'll create a 3 byte hole following the byte to keep the int 4 byte aligned. Holes in the field layout can be 1, 2 or 4 bytes in size. As fields are laid out, holes are used to avoid increasing the size of the object. Sub-classes inherit the hole information of their parent, so holes in the parent object can be reused by their children.
View Online
Changes between revision 6
and revision 7:
h2. Object Model
An _object model_ dictates how to represent objects in storage; the best object model will maximize efficiency of frequent language operations while minimizing storage overhead. Jikes RVM's object model is defined by {{VM_ObjectModel}}.
An _object model_ dictates how to represent objects in storage; the best object model will maximize efficiency of frequent language operations while minimizing storage overhead. Jikes RVM's object model is defined by {{ObjectModel}}.
h4. Overview
Values in the Java[™|Trademarks] programming language are either _primitive_ (_e.g._ {{int}}, {{double}}, etc.) or they are _references_ (that is, pointers) to objects. Objects are either _arrays_ having elements or _scalar objects{_}having fields. Objects are logically composed of two primary sections: an object header (described in more detail below) and the object's instance fields (or array elements).
The following non-functional requirements govern the Jikes RVM object model:
* instance field and array accesses should be as fast as possible,
* null-pointer checks should be performed by the hardware if possible,
* method dispatch and other frequent runtime services should be fast,
* other (less frequent) Java operations should not be prohibitively slow, and
* per-object storage overhead (ie object header size) should be as small as possible.
Assuming the reference to an object resides in a register, compiled code can access the object's fields at a fixed displacement in a single instruction. To facilitate array access, the reference to an array points to the first (zeroth) element of an array and the remaining elements are laid out in ascending order. The number of elements in an array, its _length_, resides just before its first element. Thus, compiled code can access array elements via base scaled index addressing.
The Java programming language requires that an attempt to access an object through a {{null}} object reference generates a {{NullPointerException}}. In Jikes RVM, references are machine addresses, and {{null}} is represented by address _0_. On Linux, accesses to both very low and very high memory can be trapped by the hardware, thus all null checks can be made implicit. However, the AIX[™|Trademarks] operating system permits loads from low memory, but accesses to very high memory (at small _negative_ offsets from a null pointer) normally cause hardware interrupts. Therefore on AIX only a subset of pointer dereferences can be protected by an implicit null check.
h4. Object Header
Logically, every object header contains the following components:
* *TIB Pointer:* The TIB (Type Information Block) holds information that applies to all objects of a type. The structure of the TIB is defined by {{VM_TIBLayoutConstants}}. A TIB includes the virtual method table, a pointer to an object representing the type, and pointers to a few data structures to facilitate efficient interface invocation and dynamic type checking.
* *TIB Pointer:* The TIB (Type Information Block) holds information that applies to all objects of a type. The structure of the TIB is defined by {{TIBLayoutConstants}}. A TIB includes the virtual method table, a pointer to an object representing the type, and pointers to a few data structures to facilitate efficient interface invocation and dynamic type checking.
* *Hash Code:* Each Java object has an identity hash code. This can be read by _Object.hashCode_ or in the case that this method overridden, by _System.identityHashCode_. The default hash code is usually the location in memory of the object, however, with some garbage collectors objects can move. So the hash code remains the same, space in the object header may be used to hold the original hash code value.
* *Lock:* Each Java object has an associated lock state. This could be a pointer to a lock object or a direct representation of the lock.
* *Array Length:* Every array object provides a length field that contains the length (number of elements) of the array.
* *Garbage Collection Information:* Each Java object has associated information used by the memory management system. Usually this consists of one or two mark bits, but this could also include some combination of a reference count, forwarding pointer, etc.
* *Misc Fields:* In experimental configurations, the object header can be expanded to add additional fields to every object, typically to support profiling.
An implementation of this abstract header is defined by three files: {{VM_JavaHeader}}, which supports TIB access, default hash codes, and locking; {{VM_AllocatorHeader}}, which supports garbage collection information; and {{VM_MiscHeader}}, which supports adding additional fields to all objects.
An implementation of this abstract header is defined by three files: {{JavaHeader}}, which supports TIB access, default hash codes, and locking; {{AllocatorHeader}}, which supports garbage collection information; and {{MiscHeader}}, which supports adding additional fields to all objects.
h4. Field Layout
Fields tend to be recorded in the Java class file in the order they are declared in the Java source file. We lay out fields in the order they are declared with some exceptions to improve alignment and pack the fields in the object.
Double and long fields benefit from being 8 byte aligned. Every VM_Class records the preferred alignment of the object as a whole. We lay out double and long fields first (and object references if these are 8 bytes long) so that we can avoid making holes in the field layout for alignment. We don't do this for smaller fields as all objects need to be a multiple of 4bytes in size.
Double and long fields benefit from being 8 byte aligned. Every RVMClass records the preferred alignment of the object as a whole. We lay out double and long fields first (and object references if these are 8 bytes long) so that we can avoid making holes in the field layout for alignment. We don't do this for smaller fields as all objects need to be a multiple of 4bytes in size.
When we lay out fields we may create holes to improve alignment. For example, an int following a byte, we'll create a 3 byte hole following the byte to keep the int 4 byte aligned. Holes in the field layout can be 1, 2 or 4 bytes in size. As fields are laid out, holes are used to avoid increasing the size of the object. Sub-classes inherit the hole information of their parent, so holes in the parent object can be reused by their children.
View All Revisions |
Revert To Version 6
[Less]
|
|
Posted
over 17 years
ago
by
David Grove
Page
edited by
David Grove
- "update class names."
Overview
This section describes how Jikes RVM interfaces to native code. There are three major aspects of
... [More]
this support:
JNI Functions: This is the mechanism for transitioning from native code into Java code. Jikes RVM implements the 1.1 through 1.4 JNI specifications.
Native methods: This is the mechanism for transitioning from Java code to native code. In addition to the normal mechanism used to invoke a native method, Jikes RVM also supports a more restricted syscall mechanism that is used internally by low-level VM code to invoke native code.
Integration with m-to-n threading: Attempting to get Jikes RVM's cooperative m-to-n threading model to work nicely (at all) with native code is a major challenge. We have gone through several major redesigns of the JNI support code and RVM thread system in the process. This is still a work in progress.Each of these aspects is discussed in more detail in the following sections.
JNI Functions
All of the 1.1 through 1.4 JNIEnv interface functions are implemented.
The functions are defined in the class JNIFunctions. Methods of this class are compiled with special prologues/epilogues that translate from native calling conventions to Java calling conventions and handle other details of the transition related to m-to-n threading. Currently the optimizing compiler does not support these specialized prologue/epilogue sequences so all methods in this class are baseline compiled. The prologue/epilogue sequences are actually generated by the platform-specific JNICompiler.
Invoking Native Methods
There are two mechanisms whereby RVM may transition from Java code to native code.
The first mechanism is when RVM calls a method of the class SysCall. The native methods thus invoked are defined in one of the C and C + files of the JikesRVM executable. These native methods are non-blocking system calls or C library services. To implement a syscall, the RVM compilers generate a call sequence consistent with the platform's underlying calling convention. A syscall is not a GC-safe point, so syscalls may modify the Java heap (eg. memcpy()). For more details on the mechanics of adding a new syscall to the system, see the header comments of SysCall.java. Note again that the syscall methods are NOT JNI methods, but an independent (more efficient) interface that is specific to Jikes RVM.
The second mechanism is JNI. Naturally, the user writes JNI code using the JNI interface. RVM implements a call to a native method by using the platform-specific JNICompiler to generate a stub routine that manages the transition between Java bytecode and native code. A JNI call is a GC-safe point, since JNI code cannot freely modify the Java heap.
Interactions with m-to-n Threading
See the Thread Management subsection for more details on the thread system and m-to-n threading in Jikes RVM.
There are two ways to execute native code: syscalls and JNI. A Java thread that calls native code by either mechanism will never be preempted by Jikes RVM. As far as Jikes RVM is concerned, a Java thread that enters native code has exclusive access to the underlying Processor (pthread) until it returns to Java. Of course the OS may preempt the underlying pthread; this falls beyond Jikes RVM's control.
Some activities (eg. GC) require all threads currently running Java code to halt. So what happens when one Java thread forces a GC while another Java thread is executing native code?
If the native code is a syscall, then the VM stalls until the native code returns. Thus, all syscalls should be non-blocking operations that return fairly soon. Note that a syscall is not a GC-safe point.
On Linux/x86, Jikes RVM "hijacks" certain blocking system calls and reflects them back into the VM. The VM then uses nonblocking equivalents. This handles many of the common cases of blocking native code without requiring the full complexity of the timer-based preemption mechanism that we used to use on AIX. A complete solution would consist of implementing both mechanisms on both platforms. We hope to do this in the future.
We got GNU Classpath's (JNI-based) AWT support to work by adding code to Classpath that tells GTk (the windowing toolkit Classpath uses) to use Jikes RVM's Java threading primitives instead of the pthread-based ones that it uses by default. Jikes RVM automatically tells Classpath to do this by setting the Java system property gnu.classpath.awt.gtk.portable.native.sync at boot time. If your native code uses the glib threading primitives, as GTk does, then this will work for you, too.
Implementation Details
Supporting the combination of blocking native code and m-to-n threading is inherently complicated. Unfortunately the Jikes RVM implementation is further complicated by the fact that too much of the control logic for transitions between C and Java code is embedded in the low-level, platform-specific JNICompiler classes. As a result, the code is hard to maintain and the JNI implementations on different platforms tend to diverge.
We have some ideas for a redesign that would enable more of the control logic to be embodied in shared Java code, but there are a few minor issues to be worked out. Hopefully this will happen eventually.
Missing Features
Native Libraries: JNI 1.2 requires that the VM specially treat native libraries that contain exported functions named JNI_OnLoad and JNI_OnUnload. Only JNI_OnLoad is currently implemented.
JNICompiler: The only known deficiency in JNICompiler is that the prologue and epilogues only handle passing local references to functions that expect a jobject; they will not properly handle a jweak or a regular global reference. This would be fairly easy to implement.
JavaVM interface: The JavaVM interface has GetEnv fully implemented and AttachCurrentThread partly implemented, but DestroyJavaVM, DetachCurrentThread, and AttachCurrentThreadAsDaemon are just stubbed out and return error codes.
Directly-Exported Invocation Interface Functions: These functions (GetDefaultJavaVMInitArgs, JNI_CreateJavaVM, and JNI_GetCreatedJavaVMs) are not implemented. This is because we do not provide a virtual machine library that can be linked against, nor do we support native applications that launch and use an embedded Java VM. There is no inherent reason why this could not be done, but we have not done so yet.
Things JNI Can't Handle
atexit routines: Calling JNI code via a routine run at exit time means calling back into a VM that has been shutdown. This will cause the Jikes RVM to freeze on Intel architectures.
Contributions of any of the missing functionality described here (and associated tests) would be greatly appreciated.
View Online
Changes between revision 4
and revision 5:
h2. Overview
This section describes how Jikes RVM interfaces to native code. There are three major aspects of this support:
* JNI Functions: This is the mechanism for transitioning from native code into Java code. Jikes RVM implements the 1.1 through 1.4 JNI specifications.
* Native methods: This is the mechanism for transitioning from Java code to native code. In addition to the normal mechanism used to invoke a native method, Jikes RVM also supports a more restricted syscall mechanism that is used internally by low-level VM code to invoke native code.
* Integration with m-to-n threading: Attempting to get Jikes RVM's cooperative m-to-n threading model to work nicely (at all) with native code is a major challenge. We have gone through several major redesigns of the JNI support code and RVM thread system in the process. This is still a work in progress.Each of these aspects is discussed in more detail in the following sections.
h2. JNI Functions
All of the 1.1 through 1.4 {{JNIEnv}} interface functions are implemented.
The functions are defined in the class {{VM_JNIFunctions}}. Methods of this class are compiled with special prologues/epilogues that translate from native calling conventions to Java calling conventions and handle other details of the transition related to _m-to-n_ threading. Currently the optimizing compiler does not support these specialized prologue/epilogue sequences so all methods in this class are baseline compiled. The prologue/epilogue sequences are actually generated by the platform-specific {{VM_JNICompiler}}.
The functions are defined in the class {{JNIFunctions}}. Methods of this class are compiled with special prologues/epilogues that translate from native calling conventions to Java calling conventions and handle other details of the transition related to _m-to-n_ threading. Currently the optimizing compiler does not support these specialized prologue/epilogue sequences so all methods in this class are baseline compiled. The prologue/epilogue sequences are actually generated by the platform-specific {{JNICompiler}}.
h2. Invoking Native Methods
There are two mechanisms whereby RVM may transition from Java code to native code.
The first mechanism is when RVM calls a method of the class {{VM_SysCall}}. The native methods thus invoked are defined in one of the C and C \ files of the {{JikesRVM}} executable. These native methods are non-blocking system calls or C library services. To implement a syscall, the RVM compilers generate a call sequence consistent with the platform's underlying calling convention. A syscall is not a GC-safe point, so syscalls may modify the Java heap (eg. memcpy()). For more details on the mechanics of adding a new syscall to the system, see the header comments of VM_SysCall.java. Note again that the syscall methods are NOT JNI methods, but an independent (more efficient) interface that is specific to Jikes RVM.
The first mechanism is when RVM calls a method of the class {{SysCall}}. The native methods thus invoked are defined in one of the C and C \ files of the {{JikesRVM}} executable. These native methods are non-blocking system calls or C library services. To implement a syscall, the RVM compilers generate a call sequence consistent with the platform's underlying calling convention. A syscall is not a GC-safe point, so syscalls may modify the Java heap (eg. memcpy()). For more details on the mechanics of adding a new syscall to the system, see the header comments of SysCall.java. Note again that the syscall methods are NOT JNI methods, but an independent (more efficient) interface that is specific to Jikes RVM.
The second mechanism is JNI. Naturally, the user writes JNI code using the JNI interface. RVM implements a call to a native method by using the platform-specific {{VM_JNICompiler}} to generate a stub routine that manages the transition between Java bytecode and native code. A JNI call is a GC-safe point, since JNI code cannot freely modify the Java heap.
The second mechanism is JNI. Naturally, the user writes JNI code using the JNI interface. RVM implements a call to a native method by using the platform-specific {{JNICompiler}} to generate a stub routine that manages the transition between Java bytecode and native code. A JNI call is a GC-safe point, since JNI code cannot freely modify the Java heap.
h2. Interactions with _m-to-n_ Threading
See the [Thread Management] subsection for more details on the thread system and _m-to-n_ threading in Jikes RVM.
There are two ways to execute native code: syscalls and JNI. A Java thread that calls native code by either mechanism will never be preempted by Jikes RVM. As far as Jikes RVM is concerned, a Java thread that enters native code has exclusive access to the underlying {{VM_Processor}} (pthread) until it returns to Java. Of course the OS may preempt the underlying pthread; this falls beyond Jikes RVM's control.
There are two ways to execute native code: syscalls and JNI. A Java thread that calls native code by either mechanism will never be preempted by Jikes RVM. As far as Jikes RVM is concerned, a Java thread that enters native code has exclusive access to the underlying {{Processor}} (pthread) until it returns to Java. Of course the OS may preempt the underlying pthread; this falls beyond Jikes RVM's control.
Some activities (eg. GC) require all threads currently running Java code to halt. So what happens when one Java thread forces a GC while another Java thread is executing native code?
If the native code is a {{syscall}}, then the VM stalls until the native code returns. Thus, all syscalls should be non-blocking operations that return fairly soon. Note that a {{syscall}} is _not_ a GC-safe point.
On Linux/x86, Jikes RVM "hijacks" certain blocking system calls and reflects them back into the VM. The VM then uses nonblocking equivalents. This handles many of the common cases of blocking native code without requiring the full complexity of the timer-based preemption mechanism that we used to use on AIX. A complete solution would consist of implementing both mechanisms on both platforms. We hope to do this in the future.
We got GNU Classpath's (JNI-based) AWT support to work by adding code to Classpath that tells GTk (the windowing toolkit Classpath uses) to use Jikes RVM's Java threading primitives instead of the pthread-based ones that it uses by default. Jikes RVM automatically tells Classpath to do this by setting the Java system property {{gnu.classpath.awt.gtk.portable.native.sync}} at boot time. If your native code uses the {{glib}} threading primitives, as GTk does, then this will work for you, too.
h2. Implementation Details
Supporting the combination of blocking native code and _m-to-n_ threading is inherently complicated. Unfortunately the Jikes RVM implementation is further complicated by the fact that too much of the control logic for transitions between C and Java code is embedded in the low-level, platform-specific VM_JNICompiler classes. As a result, the code is hard to maintain and the JNI implementations on different platforms tend to diverge.
Supporting the combination of blocking native code and _m-to-n_ threading is inherently complicated. Unfortunately the Jikes RVM implementation is further complicated by the fact that too much of the control logic for transitions between C and Java code is embedded in the low-level, platform-specific JNICompiler classes. As a result, the code is hard to maintain and the JNI implementations on different platforms tend to diverge.
We have some ideas for a redesign that would enable more of the control logic to be embodied in shared Java code, but there are a few minor issues to be worked out. Hopefully this will happen eventually.
h2. Missing Features
* *Native Libraries:* JNI 1.2 requires that the VM specially treat native libraries that contain exported functions named JNI_OnLoad and JNI_OnUnload. Only JNI_OnLoad is currently implemented.
* *VM_JNICompiler:* The only known deficiency in VM_JNICompiler is that the prologue and epilogues only handle passing local references to functions that expect a jobject; they will not properly handle a jweak or a regular global reference. This would be fairly easy to implement.
* *JNICompiler:* The only known deficiency in JNICompiler is that the prologue and epilogues only handle passing local references to functions that expect a jobject; they will not properly handle a jweak or a regular global reference. This would be fairly easy to implement.
* *JavaVM interface:* The JavaVM interface has GetEnv fully implemented and AttachCurrentThread partly implemented, but DestroyJavaVM, DetachCurrentThread, and AttachCurrentThreadAsDaemon are just stubbed out and return error codes.
* *Directly-Exported Invocation Interface Functions:* These functions (GetDefaultJavaVMInitArgs, JNI_CreateJavaVM, and JNI_GetCreatedJavaVMs) are not implemented. This is because we do not provide a virtual machine library that can be linked against, nor do we support native applications that launch and use an embedded Java VM. There is no inherent reason why this could not be done, but we have not done so yet.
h2. Things JNI Can't Handle
* *atexit routines:* Calling JNI code via a routine run at exit time means calling back into a VM that has been shutdown. This will cause the Jikes RVM to freeze on Intel architectures.
Contributions of any of the missing functionality described here (and associated tests) would be greatly appreciated.
View All Revisions |
Revert To Version 4
[Less]
|
|
Posted
over 17 years
ago
by
David Grove
Page
edited by
David Grove
- "update class names."
The runtime has to deal with the relatively small number of hardware signals which can be generated during Java
... [More]
execution. On operating systems other than AIX, an attempt to dereference a null value (an access to a null value manifests as a read to a small negative address outside the mapped virtual memory address space) will generate a a segmentation fault. This means that the Jikes RVM does not need to generate explicit tests guarding against dereferencing null values except on AIX and this results in faster code generationg for non-excepting code.
The RVM handles the signal and reenters Java so that a suitable Java exception handler can be identified, the stack can be unwound (if necessary) and the handler entered in order to deal with the exception. Failing location of a handler, the associated Java thread must be cleanly terminated.
The RVM actually employs software traps to generate hardware exceptions in a small number of other cases, for example to trap array bounds exceptions. Once again a software only solution would be feasible. However, since a mechanism is already in place to catch hardware exceptions and restore control to a suitable Java handler the use of software traps is relatively simple to support.
Use of a hardware handler enables the register state at the point of exception to be saved by the hardware exception catching routine. If a Java handler is registered in the call frame which generated the exception this register state can be restored before reentry, avoiding the need for the compiler to save register state around potentially excepting instructions. Register state for handlers in frames below the exception frame is automatically saved by the compiler before making a call and so can always be restored to the state at the point of call by the exception delivery code.
The RVM booter program registers signal handlers which catch SEGV and TRAP signals. These handlers save the current register state on the stack, create a special handler frame above the saved register state and return into this handler frame executing RuntimeEntrypoints.deliverHardwareException(). This method searches the stack from the excepting frame (or from the last Java frame if the exception occurs inside native code) looking for a suitable handler and unwinding frames which do not contain one. At each unwind the saved register state is reset to the state associated with the next frame. When a handler is found the delivery code installs the saved register state and returns into the handler frame at the start of the handler block.
The RVM employs some of the same code used by the hardware exception handler to implement the language primitive throw(). This primitive
requires a handler to be located and the stack to be unwound so that the handler can be entered. A throw operation is always translated into a call to RuntimeEntrypoints.athrow() so the unwind can never happens in the handler frame. Hence the register state at the point of re-entry is always saved by the call mechanism and there is no need to generate a hardware exception.
View Online
Changes between revision 1
and revision 2:
The runtime has to deal with the relatively small number of hardware signals which can be generated during Java execution. On operating systems other than AIX, an attempt to dereference a null value (an access to a null value manifests as a read to a small negative address outside the mapped virtual memory address space) will generate a a segmentation fault. This means that the Jikes RVM does not need to generate explicit tests guarding against dereferencing null values except on AIX and this results in faster code generationg for non-excepting code.
The runtime has to deal with the relatively small number of hardware signals which can be generated during Java execution. On operating systems other than AIX, an attempt to dereference a null value (an access to a null value manifests as a read to a small negative address outside the mapped virtual memory address space) will generate a a segmentation fault. This means that the Jikes RVM does not need to generate explicit tests guarding against dereferencing null values except on AIX and this results in faster code generationg for non-excepting code.
The RVM handles the signal and reenters Java so that a suitable Java exception handler can be identified, the stack can be unwound (if necessary) and the handler entered in order to deal with the exception. Failing location of a handler, the associated Java thread must be cleanly terminated.
The RVM actually employs software traps to generate hardware exceptions in a small number of other cases, for example to trap array bounds exceptions. Once again a software only solution would be feasible. However, since a mechanism is already in place to catch hardware exceptions and restore control to a suitable Java handler the use of software traps is relatively simple to support.
Use of a hardware handler enables the register state at the point of exception to be saved by the hardware exception catching routine. If a Java handler is registered in the call frame which generated the exception this register state can be restored before reentry, avoiding the need for the compiler to save register state around potentially excepting instructions. Register state for handlers in frames below the exception frame is automatically saved by the compiler before making a call and so can always be restored to the state at the point of call by the exception delivery code.
The RVM booter program registers signal handlers which catch {{SEGV}} and {{TRAP}} signals. These handlers save the current register state on the stack, create a special handler frame above the saved register state and return into this handler frame executing {{VM\_Runtime.deliverHardwareException()}}. This method searches the stack from the excepting frame (or from the last Java frame if the exception occurs inside native code) looking for a suitable handler and unwinding frames which do not contain one. At each unwind the saved register state is reset to the state associated with the next frame. When a handler is found the delivery code installs the saved register state and returns into the handler frame at the start of the handler block.
The RVM booter program registers signal handlers which catch {{SEGV}} and {{TRAP}} signals. These handlers save the current register state on the stack, create a special handler frame above the saved register state and return into this handler frame executing {{RuntimeEntrypoints.deliverHardwareException()}}. This method searches the stack from the excepting frame (or from the last Java frame if the exception occurs inside native code) looking for a suitable handler and unwinding frames which do not contain one. At each unwind the saved register state is reset to the state associated with the next frame. When a handler is found the delivery code installs the saved register state and returns into the handler frame at the start of the handler block.
The RVM employs some of the same code used by the hardware exception handler to implement the language primitive {{throw()}}. This primitive
requires a handler to be located and the stack to be unwound so that the handler can be entered. A throw operation is always translated into a call to {{VM_Runtime.athrow()}} so the unwind can never happens in the handler frame. Hence the register state at the point of re-entry is always saved by the call mechanism and there is no need to generate a hardware exception.
requires a handler to be located and the stack to be unwound so that the handler can be entered. A throw operation is always translated into a call to {{RuntimeEntrypoints.athrow()}} so the unwind can never happens in the handler frame. Hence the register state at the point of re-entry is always saved by the call mechanism and there is no need to generate a hardware exception.
View All Revisions |
Revert To Version 1
[Less]
|
|
Posted
over 17 years
ago
by
David Grove
Page
edited by
David Grove
- "update class names."
The runtime maintains a database of Java instances which identifies the currently loaded class and method base.
... [More]
The classloader class base enables the runtime to identify and dynamically load undefined classes as they required during execution. All the classes, methods and compiled code arrays required to enable the runtime to operate are pre-installed in the initial boot image. Other runtime classes and application classes are loaded dynamically as they are needed during execution and have their methods compiled lazily. The runtime can also identify the latest compiled code array (and, on occasions, previously generated versions of compiled code) of any given method via this classbase and recompile it dynamically should it wish to do so.
Lazy method compilation postpones compilation of a dynamically loaded class' methods at load-time, enabling partial loading of the class base to occur. Immediate compilation of all methods would require loading of all classes mentioned in the bytecode in order to verify that they were being used correctly. Immediate compilation of these class' methods would require yet more loading and so on until the whole classbase was installed. Lazy compilation delays this recursive class loading process by postponing compilation of a method until it is first called.
Lazy compilation works by generating a stub for each of a class' methods when the class is loaded. If the method is an instance method this stub is installed in the appropriate TIB slot. If the method is static it is placed in a linker table located in the JTOC (linker table slots are allocated for each static method when a class is dynamically loaded). When the stub is invoked it calls the compiler to compile the method for real and then jumps into the relevant code to complete the call. The compiler ensures that the relevant TIB slot/linker table slot is updated with the new compiled code array. It also handles any race conditions caused by concurrent calls to the dummy method code ensuring that only one caller proceeds with the compilation and other callers wait for the resulting compiled code.
Class Loading
Jikes™ RVM implements the Java™ programming language's dynamic class loading. While a class is being loaded it can be in one of six states. These are:
vacant: The RVMClass object for this class has been created and registered and is in the process of being loaded.
loaded: The class's bytecode file has been read and parsed successfully. The modifiers and attributes for the fields have been loaded and the constant pool has been constructed. The class's superclass (if any) and superinterfaces have been loaded as well.
resolved: The superclass and superinterfaces of this class has been resolved. The offsets (whether in the object itself, the JTOC, or the class's TIB) of its fields and methods have been calculated.
instantiated: The superclass has been instantiated and pointers to the compiled methods or lazy compilation stubs have been inserted into the JTOC (for static methods) and the TIB (for virtual methods).
initializing: The superclass has been initialized and the class initializer is being run.
initialized: The superclass has been initialized and the class initializer has been run.
Code Management
A compiled method body is an array of machine instructions (stored as ints on PowerPC™ and bytes on x86-32). The Jikes RVM Table of Contents(JTOC), stores pointers to static fields and methods. However, pointers for instance fields and instance methods are stored in the receiver class's TIB. Consequently, the dispatch mechanism differs between static methods and instance methods.
The JTOC
The JTOC holds pointers to each of Jikes™ RVM's global data structures, as well as literals, numeric constants and references to String constants. The JTOC also contains references to the TIB for each class in the system. Since these structures can have many types and the JTOC is declared to be an array of ints, Jikes RVM uses a descriptor array, co-indexed with the JTOC, to identify the entries containing references. The JTOC is depicted in the figure below.
Virtual Methods
A TIB contains pointers to the compiled method bodies (executable code) for the virtual methods and other instance methods of its class. Thus, the TIB serves as Jikes RVM's virtual method table. A virtual method dispatch entails loading the TIB pointer from the object reference, loading the address of the method body at a given offset off the TIB pointer, and making an indirect branch and link to it. A virtual method is dispatched to with the invokevirtual bytecode; other instance methods are invoked by the invokespecial bytecode.
Static Fields and Methods
Static fields and pointers to static method bodies are stored in the JTOC. Static method dispatch is simpler than virtual dispatch, since a well-known JTOC entry method holds the address of the compiled method body.
Instance Initialization Methods
Pointers to the bodies of instance initialization methods, <init>, are stored in the JTOC. (They are always dispatched to with the invokespecial bytecode.)
Lazy Method Compilation
Method slots in a TIB or the JTOC may hold either a pointer to the compiled code, or a pointer to the compiled code of the lazy method invocation stub. When invoked, the lazy method invocation stub compiles the method, installs a pointer to the compiled code in the appropriate TIB or the JTOC slot, then jumps to the start of the compiled code.
Interface Methods
Regardless of whether or not a virtual method is overridden, virtual method dispatch is still simple since the method will occupy the same TIB offset its defining class and in every sub-class. However, a method invoked through an invokeinterface call rather than an invokevirtual call, will not occupy the same TIB offset in every class that implements its interface. This complicates dispatch for invokeinterface.
The simplest, and least efficient way, of locating an interface method is to search all the virtual method entries in the TIB finding a match. Instead, Jikes RVM uses an Interface Method Table(IMT) which resembles a virtual method table for interface methods. Any method that could be an interface method has a fixed offset into the IMT just as with the TIB. However, unlike in the TIB, two different methods may share the same offset into the IMT. In this case, a conflict resolution stub is inserted in the IMT. Conflict resolution stubs are custom-generated machine code sequences that test the value of a hidden parameter to dispatch to the desired interface method. For more details, see InterfaceInvocation.
View Online
Changes between revision 2
and revision 3:
The runtime maintains a database of Java instances which identifies the currently loaded class and method base. The classloader class base enables the runtime to identify and dynamically load undefined classes as they required during execution. All the classes, methods and compiled code arrays required to enable the runtime to operate are pre-installed in the initial boot image. Other runtime classes and application classes are loaded dynamically as they are needed during execution and have their methods compiled lazily. The runtime can also identify the latest compiled code array (and, on occasions, previously generated versions of compiled code) of any given method via this classbase and recompile it dynamically should it wish to do so.
Lazy method compilation postpones compilation of a dynamically loaded class' methods at load-time, enabling partial loading of the class base to occur. Immediate compilation of all methods would require loading of all classes mentioned in the bytecode in order to verify that they were being used correctly. Immediate compilation of these class' methods would require yet more loading and so on until the whole classbase was installed. Lazy compilation delays this recursive class loading process by postponing compilation of a method until it is first called.
Lazy compilation works by generating a stub for each of a class' methods when the class is loaded. If the method is an instance method this stub is installed in the appropriate TIB slot. If the method is static it is placed in a linker table located in the JTOC (linker table slots are allocated for each static method when a class is dynamically loaded). When the stub is invoked it calls the compiler to compile the method for real and then jumps into the relevant code to complete the call. The compiler ensures that the relevant TIB slot/linker table slot is updated with the new compiled code array. It also handles any race conditions caused by concurrent calls to the dummy method code ensuring that only one caller proceeds with the compilation and other callers wait for the resulting compiled code.
Lazy compilation works by generating a stub for each of a class' methods when the class is loaded. If the method is an instance method this stub is installed in the appropriate TIB slot. If the method is static it is placed in a linker table located in the JTOC (linker table slots are allocated for each static method when a class is dynamically loaded). When the stub is invoked it calls the compiler to compile the method for real and then jumps into the relevant code to complete the call. The compiler ensures that the relevant TIB slot/linker table slot is updated with the new compiled code array. It also handles any race conditions caused by concurrent calls to the dummy method code ensuring that only one caller proceeds with the compilation and other callers wait for the resulting compiled code.
h2. Class Loading
Jikes[™|Trademarks] RVM implements the Java[™|Trademarks] programming language's dynamic class loading. While a class is being loaded it can be in one of six states. These are:
* *vacant:* The VM_Class object for this class has been created and registered and is in the process of being loaded.
* *vacant:* The RVMClass object for this class has been created and registered and is in the process of being loaded.
* *loaded:* The class's bytecode file has been read and parsed successfully. The modifiers and attributes for the fields have been loaded and the constant pool has been constructed. The class's superclass (if any) and superinterfaces have been loaded as well.
* *resolved:* The superclass and superinterfaces of this class has been resolved. The offsets (whether in the object itself, the JTOC, or the class's TIB) of its fields and methods have been calculated.
* *instantiated:* The superclass has been instantiated and pointers to the compiled methods or lazy compilation stubs have been inserted into the JTOC (for static methods) and the TIB (for virtual methods).
* *initializing:* The superclass has been initialized and the class initializer is being run.
* *initialized:* The superclass has been initialized and the class initializer has been run.
* *resolved:* The superclass and superinterfaces of this class has been resolved. The offsets (whether in the object itself, the JTOC, or the class's TIB) of its fields and methods have been calculated.
* *instantiated:* The superclass has been instantiated and pointers to the compiled methods or lazy compilation stubs have been inserted into the JTOC (for static methods) and the TIB (for virtual methods).
* *initializing:* The superclass has been initialized and the class initializer is being run.
* *initialized:* The superclass has been initialized and the class initializer has been run.
h2. Code Management
A compiled method body is an array of machine instructions (stored as ints on PowerPC[™|Trademarks] and bytes on x86-32). The _Jikes RVM Table of Contents_(JTOC), stores pointers to static fields and methods. However, pointers for instance fields and instance methods are stored in the receiver class's [TIB|Object Model#TIB]. Consequently, the dispatch mechanism differs between static methods and instance methods.
h4. The JTOC
The JTOC holds pointers to each of Jikes[™|Trademarks] RVM's global data structures, as well as literals, numeric constants and references to String constants. The JTOC also contains references to the [TIB|Object Model#TIB] for each class in the system. Since these structures can have many types and the JTOC is declared to be an array of ints, Jikes RVM uses a descriptor array, co-indexed with the JTOC, to identify the entries containing references. The JTOC is depicted in the figure below.
!jtoc.gif|title="The Jikes RVM Table Of Contents and other objects."!
h4. Virtual Methods
A [TIB|Object Model#TIB] contains pointers to the compiled method bodies (executable code) for the virtual methods and other instance methods of its class. Thus, the [TIB|Object Model#TIB] serves as Jikes RVM's virtual method table. A virtual method dispatch entails loading the TIB pointer from the object reference, loading the address of the method body at a given offset off the TIB pointer, and making an indirect branch and link to it. A virtual method is dispatched to with the _invokevirtual_ bytecode; other instance methods are invoked by the _invokespecial_ bytecode.
h4. Static Fields and Methods
Static fields and pointers to static method bodies are stored in the JTOC. Static method dispatch is simpler than virtual dispatch, since a well-known JTOC entry method holds the address of the compiled method body.
h4. Instance Initialization Methods
Pointers to the bodies of instance initialization methods, {{<init>}}, are stored in the JTOC. (They are always dispatched to with the _invokespecial_ bytecode.)
h4. Lazy Method Compilation
Method slots in a TIB or the JTOC may hold either a pointer to the compiled code, or a pointer to the compiled code of the _lazy method invocation stub_. When invoked, the lazy method invocation stub compiles the method, installs a pointer to the compiled code in the appropriate [TIB|Object Model#TIB] or the JTOC slot, then jumps to the start of the compiled code.
h4. Interface Methods
Regardless of whether or not a virtual method is overridden, virtual method dispatch is still simple since the method will occupy the same [TIB|Object Model#TIB] offset its defining class and in every sub-class. However, a method invoked through an {{invokeinterface}} call rather than an {{invokevirtual call}}, will not occupy the same [TIB|Object Model#TIB] offset in every class that implements its interface. This complicates dispatch for {{invokeinterface}}.
The simplest, and least efficient way, of locating an interface method is to search all the virtual method entries in the [TIB|Object Model#TIB] finding a match. Instead, Jikes RVM uses an _Interface Method Table_(IMT) which resembles a virtual method table for interface methods. Any method that could be an interface method has a fixed offset into the IMT just as with the TIB. However, unlike in the TIB, two different methods may share the same offset into the IMT. In this case, a _conflict resolution stub_ is inserted in the IMT. Conflict resolution stubs are custom-generated machine code sequences that test the value of a hidden parameter to dispatch to the desired interface method. For more details, see {{VM_InterfaceInvocation}}.
The simplest, and least efficient way, of locating an interface method is to search all the virtual method entries in the [TIB|Object Model#TIB] finding a match. Instead, Jikes RVM uses an _Interface Method Table_(IMT) which resembles a virtual method table for interface methods. Any method that could be an interface method has a fixed offset into the IMT just as with the TIB. However, unlike in the TIB, two different methods may share the same offset into the IMT. In this case, a _conflict resolution stub_ is inserted in the IMT. Conflict resolution stubs are custom-generated machine code sequences that test the value of a hidden parameter to dispatch to the desired interface method. For more details, see {{InterfaceInvocation}}.
View All Revisions |
Revert To Version 2
[Less]
|
|
Posted
over 17 years
ago
by
David Grove
Page
edited by
David Grove
- "update class names."
The RVM is started up by a boot program written in C. This program is responsible for
registering signal
... [More]
handlers to deal with the hardware errors generated by the RVM
establishing the initial virtual memory map employed by the RVM
mapping the RVM image files
installing the addresses of the C wrapper functions which are invoked by the runtime to interact with the underlying operating system into the boot record of at the start of the RVM image area
setting up the JTOC and PR registers for its Processor/pthread
switching the pthread into the bootstrap Java stack running the bootstrap Java method in the bootstrap Java thread
At this point all further initialization of the RVM is done either in Java or by employing the wrapper callbacks located in the boot record.
The initial bootstrap routine is VM.boot(). It sets up the initial thread and processor environment so that it looks like any other thread created by a call to Thread.start() then performs a variety of Java boot operations, including initialising the memory manager subsystem, the runtime compiler, the system classloader and the time classes.
The bootstrap routine needs to rerun class initializers for a variety of the runtime and Classpath classes which are already loaded and compiled into the image file. This is necessary because some of the data generated by these initialization routines will not be valid in the RVM runtime. The data may be invalid as the host environment that generated the boot image may differ from the current environment.
The boot process the enables the Java scheduler, creating extra pthreads to support the required number of virtual processors (VPs) and rendezvousing with them before letting them idle waiting for new Java threads. The scheduler boot routine places an idle thread IdleThread and garbage collector thread CollectorThread into the idle queue and collector queue.
Next, the boot routine boots the the JNI subsystem which enables calls to native code to be compiled and executed then re-initialises a few more classes whose init methods require a functional JNI (i.e. java.io.FileDescriptor).
Finally, the boot routine loads the boot application class supplied on the rvm command line, creates and schedules a Java main thread to execute this class's main method, then exits, switching execution to the main thread. Execution continues until the application thread and all non-daemon threads have exited. Once there are no runnable threads (other than system threads such as the idle threads, collector threads etc) execution of the RVM runtime terminates and the rvm process exits.
Memory Map
The RVM divides its available virtual memory space into various segments containing either code, or data or a combination of the two. The basic map is as follows:
--> BOOT_IMAGE_START MAX_MAPPABLE_ADDRESS <--
|<- SEGMENT_SIZE -> |
-------------------------------------------------------------------------
Platform specific| RVM Image | RVM Heap | Plat
( booter code/ ) | ( initial code )| ( meta data, immortal data )| spec
( data, shlibs ) | ( & data )| ( large & small objects )|
-------------------------------------------------------------------------
Boot Segment
The bottom segment of the address space is left for the underlying platform to locate the boot program (including statically linked library code) and any dynamically allocated data and library code.
RVM Image Segment
The next area is the one initialized by the boot program to contain the all the initial static data, instance data and compiled method code required in order for the runtime to be able to function. The required memory data is loaded from an image file created by an off line Java program, the boot image writer.
This image file is carefully constructed to contain data which, when loaded at the correct address, will populate the runtime data area with a memory image containing:
a JTOC
all the TIBs, static method code arrays and static field data directly referenced from the JTOC
all the dynamic method code arrays indirectly referenced from the TIBS
all the classloader's internal class and method instances indirectly referenced via the TIBS
ancillary structures attached to these class and method instances such as class bytecode arrays, compilation records, garbage collection maps etc
a single bootstrap processor instance on which to start Java execution
a single bootstrap Java thread instance in which Java execution commences
a single bootstrap thread stack used by the bootstrap thread.
a master boot record located at the start of the image load area containing references to all the other key objects in the image (such as the JTOC, the bootstrap thread etc) plus linkage slots in which the booter writes the addresses of its C callback functions.
RVM Heap Segment
The RVM heap segment is used to provide storage for code and data created during Java execution. The RVM can be configured to employ various different allocation managers taken from the MMTk memory management toolkit.
View Online
Changes between revision 3
and revision 4:
The RVM is started up by a boot program written in C. This program is responsible for
* registering signal handlers to deal with the hardware errors generated by the RVM
* establishing the initial virtual memory map employed by the RVM
* mapping the RVM image files
* installing the addresses of the C wrapper functions which are invoked by the runtime to interact with the underlying operating system into the boot record of at the start of the RVM image area
* setting up the JTOC and PR registers for its {{VM\_Processor}}/pthread
* setting up the JTOC and PR registers for its {{Processor}}/pthread
* switching the pthread into the bootstrap Java stack running the bootstrap Java method in the bootstrap Java thread
At this point all further initialization of the RVM is done either in Java or by employing the wrapper callbacks located in the boot record.
The initial bootstrap routine is {{VM.boot()}}. It sets up the initial thread and processor environment so that it looks like any other thread created by a call to Thread.start() then performs a variety of Java boot operations, including initialising the memory manager subsystem, the runtime compiler, the system classloader and the time classes.
The bootstrap routine needs to rerun class initializers for a variety of the runtime and Classpath classes which are already loaded and compiled into the image file. This is necessary because some of the data generated by these initialization routines will not be valid in the RVM runtime. The data may be invalid as the host environment that generated the boot image may differ from the current environment.
The boot process the enables the Java scheduler, creating extra pthreads to support the required number of virtual processors (VPs) and rendezvousing with them before letting them idle waiting for new Java threads. The scheduler boot routine places an idle thread {{VM\_IdleThread}} and garbage collector thread {{VM\_CollectorThread}} into the idle queue and collector queue.
The boot process the enables the Java scheduler, creating extra pthreads to support the required number of virtual processors (VPs) and rendezvousing with them before letting them idle waiting for new Java threads. The scheduler boot routine places an idle thread {{IdleThread}} and garbage collector thread {{CollectorThread}} into the idle queue and collector queue.
Next, the boot routine boots the the JNI subsystem which enables calls to native code to be compiled and executed then re-initialises a few more classes whose init methods require a functional JNI (i.e. {{java.io.FileDescriptor}}).
Finally, the boot routine loads the boot application class supplied on the rvm command line, creates and schedules a Java main thread to execute this class's main method, then exits, switching execution to the main thread. Execution continues until the application thread and all non-daemon threads have exited. Once there are no runnable threads (other than system threads such as the idle threads, collector threads etc) execution of the RVM runtime terminates and the rvm process exits.
h2. Memory Map
The RVM divides its available virtual memory space into various segments containing either code, or data or a combination of the two. The basic map is as follows:
{noformat}
--> BOOT_IMAGE_START MAX_MAPPABLE_ADDRESS <--
|<- SEGMENT_SIZE -> |
-------------------------------------------------------------------------
Platform specific| RVM Image | RVM Heap | Plat
( booter code/ ) | ( initial code )| ( meta data, immortal data )| spec
( data, shlibs ) | ( & data )| ( large & small objects )|
-------------------------------------------------------------------------
{noformat}
h3. Boot Segment
The bottom segment of the address space is left for the underlying platform to locate the boot program (including statically linked library code) and any dynamically allocated data and library code.
h3. RVM Image Segment
The next area is the one initialized by the boot program to contain the all the initial static data, instance data and compiled method code required in order for the runtime to be able to function. The required memory data is loaded from an image file created by an off line Java program, the boot image writer.
This image file is carefully constructed to contain data which, when loaded at the correct address, will populate the runtime data area with a memory image containing:
* a JTOC
* all the TIBs, static method code arrays and static field data directly referenced from the JTOC
* all the dynamic method code arrays indirectly referenced from the TIBS
* all the classloader's internal class and method instances indirectly referenced via the TIBS
* ancillary structures attached to these class and method instances such as class bytecode arrays, compilation records, garbage collection maps etc
* a single bootstrap processor instance on which to start Java execution
* a single bootstrap Java thread instance in which Java execution commences
* a single bootstrap thread stack used by the bootstrap thread.
* a master boot record located at the start of the image load area containing references to all the other key objects in the image (such as the JTOC, the bootstrap thread etc) plus linkage slots in which the booter writes the addresses of its C callback functions.
h3. RVM Heap Segment
The RVM heap segment is used to provide storage for code and data created during Java execution. The RVM can be configured to employ various different allocation managers taken from the [MMTk] memory management toolkit.
View All Revisions |
Revert To Version 3
[Less]
|
|
Posted
over 17 years
ago
by
David Grove
Page
edited by
David Grove
- "update class names."
The optimizing compiler uses the Bottom-Up Rewrite System (BURS) for instruction selection. BURS is
... [More]
essentially a tree pattern matching system derived from Iburg by David R. Hanson. (See "Engineering a Simple, Efficient Code-Generator Generator" by Fraser, Hanson, and Proebsting, LOPLAS 1(3), Sept. 1992.) The instruction selection rules for each architecture are specified in an architecture-specific fileslocated in $RVM_ROOT/rvm/src-generated/opt-burs/${arch}, where ${arch} is the specific instruction architecture of interest. The rules are used in generating a parser, which transforms the IR.
Each rule is defined by a four-line record, consisting of:
PRODUCTION: the tree pattern to be matched. The format of each pattern is explained below.
COST: the cost of matching the pattern as opposed to skipping it. It is a Java™ expression that evaluates to an integer.
FLAGS: The flags for the operation:
NOFLAGS: this production performs no operation
EMIT_INSTRUCTION: this production will emit instructions
LEFT_CHILD_FIRST: visit child on left-and side of production first
RIGHT_CHILD_FIRST: visit child on right-hand side of production first
TEMPLATE: Java code to emit
Each production has a non-terminal, which denotes a value, followed by a colon (":"), followed by a dependence tree that produces that value. For example, the rule resulting in memory add on the INTEL architecture is expressed in the following way:
stm: INT_STORE(INT_ADD_ACC(INT_LOAD(r,riv),riv),OTHER_OPERAND(r, riv))
ADDRESS_EQUAL(P(p), PLL(p), 17)
EMIT_INSTRUCTION
EMIT(MIR_BinaryAcc.mutate(P(p), IA32_ADD, MO_S(P(p), DW), BinaryAcc.getValue(PL(p))));
The production in this rule represents the following tree:
r riv
\ /
INT_LOAD riv
\ /
INT_ADD_ACC r riv
\ | /
INT_STORE
where r is a non-terminal that represents a register or a tree producing a register, riv is a non-terminal that represents a register (or a tree producing one) or an immediate value, and INT_LOAD, INT_ADD_ACC and INT_STORE are operators (terminals). OTHER_OPERAND is just an abstraction to make the tree binary.
There are multiple helper functions that can be used in Java code (both cost expressions and generation templates). In all code sequences the name p is reserved for the current tree node. Some of the helper methods are shortcuts for accessing properties of tree nodes:
P(p) is used to access the instruction associated with the current (root) node,
PL(p) is used to access the instruction associated with the left child of the current (root) node (provided it exists),
PR(p) is used to access the instruction associated with the right child of the current (root) node (provided it exists),
similarly, PLL(p), PLR(p), PRL(p) and PRR(p) are used to access the instruction associated with the left child of the left child, right child of the left child, left child of the right child and right child of the right child, respectively, of the current (root) node (provided they exist).
What the above rule basically reads is the following:
If a tree shown above is seen, evaluate the cost expression (which, in this case, calls a helper function to test whether the addresses in the STORE (P(p)) and the LOAD (PLL(p)) instructions are equal. The function returns 17 if they are, and a special value INFINITE if not), and if the cost is acceptable, emit the STORE instruction (P(p)) mutated in place into a machine-dependent add-accumulate instruction (IA32_ADD) that adds a given value to the contents of a given memory location.
The rules file is used to generate a file called ir.brg, which, in turn, is used to produce a file called BURS_STATE.java.
For more information on helper functions look at BURS_Helpers.java. For more information on the BURS algorithm see BURS.java.
Future directions
Whilst jburg allows us to do good instruction selection there are a number of areas where it is lacking:
Vector operations
We can't write productions for vector operations unless we match an entire tree of operations. For example, it would be nice to write a rule of the form:
(r, r): ADD(r,r), ADD(r,r)
if say the architecture supported a vector add operation (ie SIMD). Unfortunately we can't have tuples on the LHS of expressions and the comma represents that matching two coverings is necessary. Leupers has shown how with a modified BURS system they can achieve this result. Their syntax is:
r: ADD(r,r)
r: ADD(r,r)
Rainer Leupers, Code selection for media processors with SIMD instructions, 2000
View Online
Changes between revision 2
and revision 3:
The optimizing compiler uses the Bottom-Up Rewrite System (BURS) for instruction selection. BURS is essentially a tree pattern matching system derived from Iburg by David R. Hanson. (See "Engineering a Simple, Efficient Code-Generator Generator" by Fraser, Hanson, and Proebsting, LOPLAS 1(3), Sept. 1992.) The instruction selection rules for each architecture are specified in an architecture-specific fileslocated in {{$RVM_ROOT/rvm/src-generated/opt-burs/$\{arch\}}}, where $\{arch\} is the specific instruction architecture of interest. The rules are used in generating a parser, which transforms the IR.
Each rule is defined by a four-line record, consisting of:
* {{PRODUCTION}}: the tree pattern to be matched. The format of each pattern is explained below.
* {{COST}}: the cost of matching the pattern as opposed to skipping it. It is a Java[™| Trademarks] expression that evaluates to an integer.
* {{FLAGS}}: The flags for the operation:
** NOFLAGS: this production performs no operation
** EMIT_INSTRUCTION: this production will emit instructions
** LEFT_CHILD_FIRST: visit child on left-and side of production first
** RIGHT_CHILD_FIRST: visit child on right-hand side of production first
** RIGHT_CHILD_FIRST: visit child on right-hand side of production first
* {{TEMPLATE}}: Java code to emit
Each production has a _non-terminal_, which denotes a value, followed by a colon (":"), followed by a dependence tree that produces that value. For example, the rule resulting in memory add on the INTEL architecture is expressed in the following way:
{noformat}
stm: INT_STORE(INT_ADD_ACC(INT_LOAD(r,riv),riv),OTHER_OPERAND(r, riv))
ADDRESS_EQUAL(P(p), PLL(p), 17)
EMIT_INSTRUCTION
EMIT(MIR_BinaryAcc.mutate(P(p), IA32_ADD, MO_S(P(p), DW), BinaryAcc.getValue(PL(p))));
{noformat}
The production in this rule represents the following tree:
{noformat}
r riv
\ /
INT_LOAD riv
\ /
INT_ADD_ACC r riv
\ | /
INT_STORE
{noformat}
where {{r}} is a non-terminal that represents a register or a tree producing a register, {{riv}} is a non-terminal that represents a register (or a tree producing one) or an immediate value, and {{INT_LOAD}}, {{INT_ADD_ACC}} and {{INT_STORE}} are operators (_terminals_). {{OTHER_OPERAND}} is just an abstraction to make the tree binary.
There are multiple helper functions that can be used in Java code (both cost expressions and generation templates). In all code sequences the name {{p}} is reserved for the current tree node. Some of the helper methods are shortcuts for accessing properties of tree nodes:
* {{P(p)}} is used to access the instruction associated with the current (root) node,
* {{PL(p)}} is used to access the instruction associated with the left child of the current (root) node (provided it exists),
* {{PR(p)}} is used to access the instruction associated with the right child of the current (root) node (provided it exists),
* similarly, {{PLL(p)}}, {{PLR(p)}}, {{PRL(p)}} and {{PRR(p)}} are used to access the instruction associated with the left child of the left child, right child of the left child, left child of the right child and right child of the right child, respectively, of the current (root) node (provided they exist).
What the above rule basically reads is the following:
If a tree shown above is seen, evaluate the cost expression (which, in this case, calls a helper function to test whether the addresses in the {{STORE}} ({{P(p)}}) and the {{LOAD}} ({{PLL(p)}}) instructions are equal. The function returns 17 if they are, and a special value {{INFINITE}} if not), and if the cost is acceptable, emit the {{STORE}} instruction ({{P(p)}}) mutated in place into a machine-dependent add-accumulate instruction ({{IA32_ADD}}) that adds a given value to the contents of a given memory location.
The rules file is used to generate a file called {{ir.brg}}, which, in turn, is used to produce a file called {{OPT_BURS_STATE.java}}.
The rules file is used to generate a file called {{ir.brg}}, which, in turn, is used to produce a file called {{BURS_STATE.java}}.
For more information on helper functions look at {{OPT_BURS_Helpers.java}}. For more information on the BURS algorithm see {{OPT_BURS.java}}.
For more information on helper functions look at {{BURS_Helpers.java}}. For more information on the BURS algorithm see {{BURS.java}}.
h2. Future directions
Whilst jburg allows us to do good instruction selection there are a number of areas where it is lacking:
h3. Vector operations
We can't write productions for vector operations unless we match an entire tree of operations. For example, it would be nice to write a rule of the form:
{noformat}
(r, r): ADD(r,r), ADD(r,r)
{noformat}
if say the architecture supported a vector add operation (ie SIMD). Unfortunately we can't have tuples on the LHS of expressions and the comma represents that matching two coverings is necessary. [Leupers|#leupers] has shown how with a modified BURS system they can achieve this result. Their syntax is:
{noformat}
r: ADD(r,r)
r: ADD(r,r)
{noformat}
{anchor:leupers}
* [Rainer Leupers, Code selection for media processors with SIMD instructions, 2000|http://doi.acm.org/10.1145/343647.343679]
View All Revisions |
Revert To Version 2
[Less]
|