Lazy Hackers Guide To Porting

From Apache OpenOffice Wiki
Jump to: navigation, search

A Quick Guide to Porting to an unsupported Linux Architecture

Easy Bits

1. Edit set_soenv.in and add an entry for your platform with a name based on ./config.guess e.g. copy from the simple m68k entry and adapt it. Pick a non-conflicting CPU, CPUNAME and OUTPATH. (here an OUTPATH of unxlnghppa is Unix LiNux GCC on HPPA). The default "parisc" section in the JRE variables should default to uname -m unless the standard JRE on the platform is known to be different.

elsif ($platform =~ m/^hppa/)
{  print "Setting Linux hppa specific values... ";
   $outfile        = "LinuxHPPAEnv.Set";
   $CPU            = "H";
   $CPUNAME        = "HPPA";
   $OUTPATH        = "unxlnghppa";
   $JRELIBDIR      = '$JAVA_HOME'.$ds."jre".$ds."lib".$ds."parisc";
   $JRETOOLKITDIR  = '$JAVA_HOME'.$ds."jre".$ds."lib".$ds."parisc".$ds."server";
   $JRETHREADDIR   = '$JAVA_HOME'.$ds."jre".$ds."lib".$ds."parisc".$ds."native_threads";
}

2. Edit solenv/inc/unx.mk and an add an entry for your platform based off the OS and the new CPU you added to set_soenv.in to include a new makefile for that platform

.IF "$(COM)$(OS)$(CPU)" == "GCCLINUXH"
.INCLUDE : unxlnghppa.mk
.ENDIF


3. Create the .mk for the platform using the name set in 2, e.g. copy unxlngm68k.mk to it and make appropriate changes, i.e. pick a DLL suffix, set the correct PICSWITCH for your platform and add -D$CPUNAME

# mk file for Unix Linux HPPA using GCC, please make generic modifications to unxlng.mk
PICSWITCH:=-fPIC
.INCLUDE : unxlng.mk
CDEFS+=-DHPPA
CFLAGS+=
CFLAGSCC+=
CFLAGSCXX+=
DLLPOSTFIX=lh

4. Edit sal/rtl/source/macro.hxx and add an entry for your platform (used to determine what OOo extensions could be used with the end-product), hook it off the -D$CPUNAME you used in 3.

#elif defined HPPA
#    define THIS_ARCH "HPPA"
#endif

5. You might have to edit sal/typesconfig/typesconfig.c for some platforms where unaligned data accesses are allowed but are not desired, e.g. some arm kernels (and always under qemu I believe ?) allow misaligned memory accesses, but its a massively inefficient kernel-fixup type of thing and some arm kernels disallow it. ia64 is another platform like this where typeconfig would not be disallowed from accessing unaligned memory, but we want to over ride that and force it to not do so.

#if defined(IA64) || defined(ARM32) || defined(HPPA)

6. Edit tools/inc/tools/solar.h and add an entry that matches the suffix chosen in 3

#elif defined LINUX && defined HPPA
 #define __DLLEXTENSION "lh.so"

7. Edit automation/source/testtool/objtest.cxx and add an entry for your platform + arch

#elif defined LINUX && defined HPPA
   abGP.Append( "22" );  // Linux PA-RISC
#else

8. Edit jvmfwk/plugins/sunmajor/pluginlib/vendorbase.hxx and add an entry for your platform + arch (this entry should follow the arch name of the arch jvm of 1, i.e. /usr/lib/jvm/java-version/jre/lib/ARCH which is generally the output of uname -m

#elif defined HPPA
 #define JFW_PLUGIN_ARCH "parisc"

9. Edit desktop/source/deployment/misc/dp_platform.cxx and add an entry for your platform + arch reusing the same info as in 4

Hard Bit

9. In bridges/source/cpp_uno create a dir for you arch, e.g. cp -r gcc_linux_m68k to gcc_linux_hppa and edit the makefile.mk in your new dir to match 1 and 2 and then make the changes needed to make your platform work you'll probably get lucky and not have to modify the except stuff, they're all the same for all linuxes except for ARM-eabi if I recall correctly.

10. Edit bridges/prj/build.lst and add an entry for this dir

br      bridges\source\cpp_uno\gcc3_linux_hppa          nmake   -       u       br_gccl3h br_unotypes NULL

11. codeSnippet at runtime generates the code needed to trampoline to a specified handler. the intent is that calls to virtual methods of a proxy object get routed to that handler along with all the original arguments passed to the virtual method, with the additional information as to what is the index of this virtual method and the offset of the vtable.

It varies in complexity from the trivial ppc64 one where the abi is particularly convenient to more complicated ones like the sparc one.

To take hppa as an example we start off just trying to get a trampoline to work to jump to a handler on virtual method invocation, e.g. a test.s of

b,l .+8,%r21        ; %r21 <- pc+8 */
depi 0,31,2,%r21    ; mask priv bits */
ldw 16(%r21),%r1    ; load plabel where 16 is the relative offset to where we've copied the address of the handler*/

ldw 0(%r1),%r23     ; address of handler */
bv %r0(%r23)        ; branch to handler */
ldw 4(%r1),%r19     ; GP of handler */
ldw 4(%r1),%r19     ; place address of handler here
as test.s -o test.o
objdump -d test.o
Disassembly of section .text:

00000000 <.text>:
   0:  ea a0 00 00     b,l 0x8,r21 
   4:  d6 a0 1c 1e     depwi 0,31,2,r21
   8:  4a a1 00 20     ldw 20(r21),r1
   c:  0c 20 10 97     ldw 0(r1),r23
   10: ea e0 c0 00     bv r0(r23) 
  14:  0c 28 10 93     ldw 4(r1),r19

So whack this into codeSnippet and tweak it

*(unsigned long*)&p[0] = 0xeaa00000;  /* b,l .+8,%r21        ; %r21 <- pc+8 */
*(unsigned long*)&p[4] = 0xd6a01c1e;  /* depi 0,31,2,%r21    ; mask priv bits */
*(unsigned long*)&p[8] = 0x4aa10020;  /* ldw 20(%r21),%r1    ; load plabel */

*(unsigned long*)&p[12] = 0x0c201097; /* ldw 0(%r1),%r23     ; address of handler */
*(unsigned long*)&p[16] = 0xeae0c000; /* bv %r0(%r23)        ; branch to handler */
*(unsigned long*)&p[20] = 0x0c281093; /* ldw 4(%r1),%r19     ; GP of handler */
*(unsigned long*)&p[24] = ((unsigned long)(cpp_vtable_call) & ~2);

make cpp_vtable_call(void) output hello world or whatever

cd testtools
build

You should see that "hello world" before it falls over and breaks.

12. You'll possibly need a implementation of flushCode. Almost definitely if its some form of RISC in order to flush the icache/dcache, e.g. HPPA version is something like...

void bridges::cpp_uno::shared::VtableFactory::flushCode(
   unsigned char const *beg, unsigned char const *end)
{
   void *p = (void*)((size_t)beg & ~31);
   size_t stride = 32;
   while (p < end)
   {
       asm volatile("fdc (%0)\n\t"
                    "sync\n\t"
                    "fic,m %1(%%sr4, %0)\n\t"
                    "sync" : "+r"(p) : "r"(stride) : "memory");
   }
}

13. Smuggle in the extra functionIndex and vtableOffset values into cpp_vtable_call. Taking the hppa example the abi docs say that register r21 and r22 aren't used for passing arguments and the caller needs to save them, so we can use those probably, i.e..

void cpp_vtable_call( void )
{
   register unsigned long r21 asm("r21");
   register unsigned long r22 asm("r22");
   unsigned long functionIndex = r21;
   unsigned long vtableOffset = r22;

   fprintf(stderr, "got to cpp_vtable_call with %x %x\n", functionIndex, vtableOffset);
}
and munge the extra values we want into these registers
const int codeSnippetSize = 44;

#define unldil(v) (((v & 0x7c) << 14) | ((v & 0x180) << 7) | ((v & 0x3) << 12) | ((v & 0xffe00) >> 8) | ((v & 0x100000) >> 20))
#define L21(v)  unldil(((unsigned long)(v) >> 11) & 0x1fffff) //Left 21 bits
#define R11(v)  (((unsigned long)(v) & 0x7FF) << 1) //Right 11 bits

unsigned char *codeSnippet(unsigned char* code, sal_Int32 functionIndex, 
    sal_Int32 vtableOffset, bool bHasHiddenParam)
{
    if (bHasHiddenParam)
        functionIndex |= 0x80000000;

    unsigned char * p = code;
    *(unsigned long*)&p[0]  = 0xeaa00000; // b,l 0x8,r21
    *(unsigned long*)&p[4]  = 0xd6a01c1e; // depwi 0,31,2,r21
    *(unsigned long*)&p[8]  = 0x4aa10040; // ldw 32(r21),r1

    *(unsigned long*)&p[12] = 0x22A00000 | L21(functionIndex); // ldil L<functionIndex>,r21
    *(unsigned long*)&p[16] = 0x36B50000 | R11(functionIndex); // ldo R<functionIndex>,r21

    *(unsigned long*)&p[20] = 0x22C00000 | L21(vtableOffset); // ldil L<vtableOffset>,r22
    *(unsigned long*)&p[24] = 0x36D60000 | R11(vtableOffset); // ldo R<vtableOffset>,r22

    *(unsigned long*)&p[28] = 0x0c201094; // ldw 0(r1),r20
    *(unsigned long*)&p[32] = 0xea80c000; // bv r0(r20)
    *(unsigned long*)&p[36] = 0x0c281093; // ldw 4(r1),r19
    *(unsigned long*)&p[40] = ((unsigned long)(cpp_vtable_call) & ~2);

    return code + codeSnippetSize;
}  

A build in testtools should show we're getting reasonable values for functionIndex and vtableOffset, before it falls over and dies. So now to gather the normal arguments, for hppa the abi docs say that there are 4 general purpose registers for passing arguments, so ignoring floating points for now we can do...

void cpp_vtable_call( sal_uInt32 in0, sal_uInt32 in1, sal_uInt32 in2, sal_uInt32 in3, sal_uInt32 firstonstack )
{
    register sal_Int32 r21 asm("r21");
    register sal_Int32 r22 asm("r22");
    sal_Int32 functionIndex = r21;
    sal_Int32 vtableOffset = r22;  

    long sp = (long)&firstonstack;

    sal_uInt32 gpreg[hppa::MAX_GPR_REGS];
    gpreg[0] = in0;
    gpreg[1] = in1;
    gpreg[2] = in2;
    gpreg[3] = in3;  

    double fpreg[hppa::MAX_SSE_REGS]; //todo

#ifdef CMC_DEBUG
   fprintf(stderr, "got to cpp_vtable_call with %x %x\n", functionIndex, vtableOffset);
   for (int i = 0; i < hppa::MAX_GPR_REGS; ++i)
       fprintf(stderr, "reg %d is %d\n", i, gpreg[i]);
#endif

}

i.e. stuff the values of the 4 registers into gpreg, and get the stack pointer by taking the address of the first argument which we know should be on the stack. We dump the contents of the registers, back in testtools, the first call from c++ to uno is at xLBT->setValues in bridgetest.cxx, so if we dump the first few args there and compare to the above, then we should see the same values appearing (modulo the "this" argument which will be passed in as the first arg before the rest)

14. We just want enough code in cppcall to get this to compile and to get the pAdjustedThisPtr through to callVirtualMethod, so fill enough code in to make things compile and focus on getting through to uno2cpp.cxx's callVirtualMethod, i.e. make sure the pThis argument is derived correctly for your arch and basically forget about anything else.

void callVirtualMethod(void * pThis, sal_uInt32 nVtableIndex,
   void * pRegisterReturn, typelib_TypeClass eReturnType,
   sal_uInt32 *pStack, sal_uInt32 nStack)
{
   sal_uInt32 pMethod = *((sal_uInt32*)pThis);
   pMethod += 4 * nVtableIndex;
   pMethod = *((sal_uInt32 *)pMethod);

   typedef void (* FunctionCall )( sal_uInt32, sal_uInt32, sal_uInt32, sal_uInt32 );
   FunctionCall pFunc = (FunctionCall)pMethod;

   (*pFunc)(pStack[0], 1, 2, 3);
}

In this case we're assuming we've based this on the m68k one where everything is passed on the stack, so the "this" argument (which is the only one correct at the moment) is the first on our passed in stack, this should get Test_impl::setValues called with 1 2 3 as the first three arguments, before it falls over and dies.

15. Assuming that works, then we're basically done, except for house keeping. We can go back and using the ABI docs for the platform now correctly organize the rest of the arguments into their appropriate registers and stack positions, return conventions for various sized structs etc. Leaving floating points as the last thing to be done.

e.g. for large/complex return types hppa passes the address to place the return value into as r28, which is similar to ia64 or m68k as opposed to other platforms which push a prepended argument into registers/stack before the this pointer.

Other Information

Documentation caution.png floats and doubles are typically returned in floating point registers rather than general ones and typically there is no other use of floats/doubles inside a uno bridge, so it might be that the contents of the floating point registers are untouched between the destination method return of callVirtualMethod and the source method return of cpp_vtable_call, i.e. the contents pass through the bridge untouched and the correct results pop out. That's a bit dubious really, a few bridges may have this problem lurking in them, far better to of course explicitly save the correct double/float return into the storage provided in callVirtualMethod and set them explicitly in cpp_vtable_call
Tip.png In UNO while arbitrary struct and types can be returned, the idl compiler always generates code where structs are passed in by reference, so you don't have to worry about any struct passing rules, only struct return ones


Tip.png There's a fallback implementation in sal/osl/unx/interlck.c to use pthreads for interlocks, ports to living architectures should probably fill in a better custom implementation there
Personal tools