UML Architecture

From MAMEDEV Wiki
Revision as of 20:24, 13 May 2008 by Aaron (talk | contribs) (New page: This article describes the Universal Machine Language runtime architecture. == Machine Architecture == At its heart, the Universal Machine Language describes an abstract, primarily 32-bi...)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

This article describes the Universal Machine Language runtime architecture.

Machine Architecture

At its heart, the Universal Machine Language describes an abstract, primarily 32-bit computer architecture. It has been designed with several goals in mind:

  • dynamic recompilers should be able to express common operations simply
  • 64-bit integer operations should be supported, even if they are not preferred
  • creating x86 and PowerPC back-ends (both 32-bit and 64-bit) should be relatively straightforward
  • a back-end written in a high-level language such as C should have reasonable performance

In addition to a collection of opcodes, described below, the Universal Machine Language also describes an abstract runtime architecture with several basic requirements:

  • 10 64-bit integer registers (i0-i9)
  • 10 64-bit floating point registers (f0-f9)
  • 10 32-bit "map variables" (m0-m9) which map values onto sections of code
  • 5 flag bits that can be optionally set on most instructions
  • 1 internal exception parameter register
  • a 16-entry call stack for subroutine and exception handling

Because each back-end targets a different final CPU architecture, these abstract requirements may not map perfectly; however, it is the job of the back-end code generator to provide an implementation that fully supports all of these requirements. For example, there may not be enough free actual system registers to hold 10 64-bit values, so some of those registers may be implicitly converted by the backend into memory references. More details on how to provide these abstractions will be available in the Back-End Author's Guide.

Code Cache

One of the primary features of a dynamic recompiler is its ability to cache and quickly recall already-translated code. Because of this, the concept of a code cache is central to the UML. The code cache not only contains all the generated code, along with the necessary hash tables to find it, but it also serves as a general heap for any data referenced by the generated code. Memory can be allocated from the cache and thus kept in the vicinity of the code that is likely to reference it. On many architectures, memory that is close to the code can be more efficiently accessed, so it is important to make good use of the memory management provided by the cache.

Code Generation

UML code is generated in blocks. A block of UML code is defined to be self-contained. That is, all local jumps within the code are resolved, and all calls or jumps to code outside of the block are performed via either handles or hashes. This implies that the first instruction of a block must be either a HANDLE or a HASH opcode, and also implies that the final instruction of a block should be either a RET or a HASHJMP opcode. The specific behaviors of the opcodes are described later.

A code handle is a globally accessible reference to a block of code. In practice, a handle is allocated from the near cache and contains