[Webkit-unassigned] [Bug 24986] ARM JIT port

bugzilla-daemon at webkit.org bugzilla-daemon at webkit.org
Fri Jun 19 15:07:45 PDT 2009


https://bugs.webkit.org/show_bug.cgi?id=24986





------- Comment #43 from barraclough at apple.com  2009-06-19 15:07 PDT -------
Hi Zoltan, Gabor,

> Besides, Gabor wrote the post you answered :)

Bah, sorry guys, easy to get confused between posts on a large bug like this.
:o)

Okay, so to be clear, I do completely understand what a constant pool is, why
one is required on arm (and on other platforms, too) and the fact that for
certain operations it is necessary to generate multiple machine instructions. 
And functionally, the way you have implemented this all looks great.  The place
that I think your patch is somewhat at conflict with the design of the JIT is
just in the layering, and I think that if we don't bring the interfaces into
line with each other then we will find it more difficult to share code across
platforms, and to avoid changes we make from breaking the arm port of the JIT
as we go forwards.

I've not done a good job anywhere of trying to describe the layers in the
architecture, so let me do a better job now.  The abstract code generation
layer (MacroAssembler interface down) is layered like a traditional compiler. 
In a traditional compiler, it is common to have an assembler layer completely
independent of the compiler (often a separate application).  The compiler takes
a source code file, compiles it, and produces an output file of assembly code. 
The interface to the assembler is largely made up of a set of assembly code
mnemonics from which there is typically a one-to-one mapping to machine
instructions (additionally there are assembler directives).

Let's look at an example:

arith.c: (compiled will gcc 4.0 -O3 -march=armv6)

     int sub(int x)
    {
        return x - 12345678;
    }

     int mul(int x)
    {
         return x * 12345678;
    }

arith.s:  (With some .globl & .align directives stripped out to make it more
readable.)

    _sub:
        sub     r0, r0, #12320768
        sub     r0, r0, #24832
        sub     r0, r0, #78
        bx      lr

    _mul:
        ldr     r3, L5
        mul     r0, r0, r3
        bx      lr

    L5:
        .long   12345678

In the case of the subtract, since the immediate does not fit within an
immediate field of a subtract instruction, the C compiler has emitted multiple
subtract instructions.  In the case of the multiply, the compiler has chosen to
store the constant in a constant pool (labelled L5), and has planted a load
instruction to load the constant from the pool.  These are two different
instruction selection strategies, and a compiler may choose (as it has here) to
use a mixture of both.  The constant-pool is not a mechanism implemented within
the assembler, but instead is a one of a number of techniques available to the
compiler during instruction selection when generating code for which a single
assembly operation is not available.  The assembler is not aware of the
structure or semantics of the constant pool - it only sees a PC relative load
instruction.

Layering the compiler on top of an assembler in this fashion provides a number
of benefits.  For the compiler developer, layering the compiler on the
assembler separates the instruction selection from the minutiae of machine
instruction encoding.  For clients of the compiler providing a well defined
language for machine instruction generation is useful if the compiler provides
facilities to bypass the higher level language, and directly emit a specific
sequence of machine instructions (commonly though use of inline 'asm'
statements in C code, and in our JIT through direct use of 'm_assembler').

The assembler interface within the JIT is designed to closely mimic that of the
assembler layer in a traditional compiler, providing an interface largely made
up of the set of assembler mnemonics of the host architecture, with a
one-to-one mapping from calls to these functions, to machine instructions being
planted.  All instruction selection is performed within the MacroAssembler
layer.  The MacroAssembler is a very simple compiler, mapping from one
(generic) assembly like language to another (concrete, machine-specific)
assembly language.  We refer to it as a MacroAssembler since the capabilities
are presently very limited, simply remapping mnemonic names (e.g. addPtr ->
addq_rr), and expanding single MacroAssembler calls to multiple assembler
operations (facilities typically within the capabilities of traditional
macro-assembler languages).  However we certainly expect the sophistication of
the MacroAssembler layer to increase as necessary – and we tend to be
conservative in our naming, bear in mind that we initially labelled the entire
JIT as just a context-threaded-interpreter, a much simpler form of dynamic code
generation engine (and we still have the acronym 'cti' scattered through the
code as a reminder!).


The key difference from the ARM port as it currently stands, and the
description above of the design of the layering in the JIT (and in a
traditional compiler stack) is that the constant pool mechanism is below the
assembler interface.  It seems that correcting this so that it falls within the
MacroAssembler layer (from where we can look at how we can generalize to code
to share implementation where possible and minimize unnecessary duplication
when we introduce constant pools on other platforms) should only require a
relatively minor restructuring of your code – it should not require you to
change the code your JIT generates, and it should not significantly change the
implementation of the constant pool, just shuffle a few methods between files,
and change the classes upon which a few variables are declared.

Obviously how you fix this is entirely up to you, but here would be one way to
do it, which is basically just a bit of copying and pasting, and a bunch of
renames:

(1) Move the new methods and variables implementing the constant pool that you
have added to AssemblerBuffer up into ARMAssembler, so that AssemblerBuffer is
just a simple data container.
(2) Create a new class called ARMInstructionEncoder that contains an member
variable that is an AssemblerBuffer, and passes all AssemblerBuffer methods
through.  Replace the AssemblerBuffer member variable in ARMAssembler with a
member of type ARMInstructionEncoder.
(3) Move all the methods from ARMAssembler that format up machine instructions
onto ARMInstructionEncoder.  ARMInstructionEncoder should end up with one
method for every mnemonic in the ARM instruction set, each of which emits one
machine instruction.
(4) Rename the class ARMAssembler to MacroAssemblerARMInternals.
(5) Rename the class ARMInstructionEncoder to ARMAssembler.
(6) Move the class MacroAssemblerARMInternals into the file
MacroAssemblerARM.h.

This would put the constant-pool implementation and instruction selection on
the right side of the assembler interface.  Of course you may want to achieve
this refactoring through a completely different route than the one I've
described. The important thing is the result, this is where we really need to
get the code to so that we can look at refactoring out the constant pool so
that it can be shared across platforms, and so that we can bring the assembler
interfaces in line with each other so that changes we make do not break the ARM
JIT.

cheers,
G.


-- 
Configure bugmail: https://bugs.webkit.org/userprefs.cgi?tab=email
------- You are receiving this mail because: -------
You are the assignee for the bug, or are watching the assignee.



More information about the webkit-unassigned mailing list