| /* powerpc-sys5.s -- assembly support. */ | |
| /* | |
| * QuickThreads -- Threads-building toolkit. | |
| * Copyright (c) 1993 by David Keppel | |
| * | |
| * Permission to use, copy, modify and distribute this software and | |
| * its documentation for any purpose and without fee is hereby | |
| * granted, provided that the above copyright notice and this notice | |
| * appear in all copies. This software is provided as a | |
| * proof-of-concept and for demonstration purposes; there is no | |
| * representation about the suitability of this software for any | |
| * purpose. | |
| * PowerPC-System V thread switching module. | |
| * | |
| * This software is largely based on the original PowerPC-Linux porting | |
| * developed by Ken Aaker <kenaaker@silverbacksystems.com> | |
| * | |
| * Marco Bucci <marco.bucci@inwind.it> | |
| * December 2002 | |
| * | |
| */ | |
| /* | |
| * | |
| * PowerPC Register convections: | |
| * | |
| * r0 volatile | |
| * r1 SP | |
| * r2 system reserved | |
| * r3-r4 volatile for parameter passing and function return | |
| * r5-r10 volatile for parameter passing | |
| * r11-r12 volatile | |
| * r13-r14 non volatile registers | |
| * f0 volatile | |
| * f1 volatile for parameter passing and function return | |
| * f2-f13 volatile for parameter passing | |
| * f14-f31 non volatile | |
| * | |
| * cr2-cr4 non volatile | |
| * | |
| * | |
| * See on the heather file for more documentation. | |
| * | |
| * | |
| * | |
| * IMPLEMENTATION NOTES | |
| * | |
| * | |
| * 1) Condition register saving | |
| * On most machines, the condition code register is caller-save. | |
| * On the PPC, the condition code register is callee-save, so the | |
| * thread context switch must preserve it. | |
| * | |
| * | |
| * 2) Floating point registers saving | |
| * On resuming a thread, floating point registers are or not restored just | |
| * depending on which block routine suspended the thread (i.e. regardless | |
| * whether "qt_block", "qt_blocki" or "qt_abort" is used to resume it). | |
| * This behaviour is obtained by implementing "qt_block" by means af a nested | |
| * call to "qt_blocki". As a result, the blocking of a thread always goes | |
| * and returns through "qt_blocki and, if a thread was blocked by "qt_block", | |
| * its execution resumes from the floating point restoring code on exit | |
| * of "qt_block". | |
| * | |
| * Thanks to David Keppel that explained me this "simple" trick. | |
| * | |
| * | |
| * 3) C languace code debugging | |
| * The original version of this software was developed and debugged under | |
| * MacOS X using the Metrowerks Code Warrior PPC integrated assembler. | |
| * It could be still used with a C inline assembler by means of a suitable | |
| * file to include it. | |
| * In order to avoid "copy and paste" bugs, and make easyer the maintaining, | |
| * I made the minimal changes, so you can find some strange code as: | |
| * | |
| * #if 0 | |
| * .if 0 | |
| * C code here | |
| * .endif | |
| * #endif | |
| * | |
| * This is just to embed some C code that is needed by the Code Warrior | |
| * integrated assembler. | |
| * | |
| * | |
| * 4) Assembly constants generation | |
| * Constants used in the assembly code are generated by running | |
| * the C code in the sequel (commented). It uses the C macros declared in | |
| * the C heather in order to guarantee that the C interface and the assebly | |
| * code are "aligned". I avoided the use of an assebler preprocessor since | |
| * they are not so standard and moreover using macro espressions makes the | |
| * assembly debugging more difficult. | |
| * | |
| * | |
| #include <iostream> | |
| #include "powerpc_sys5.h" | |
| int main() | |
| { | |
| using namespace std; | |
| int i; | |
| cout << ".set LR_SAVE, " << PPC_LR_SAVE << endl; | |
| cout << ".set BLOCKI_FSIZE, " << QUICKTHREADS_BLOCKI_FRAME_SIZE << endl; | |
| cout << ".set BLOCKI_CR_SAVE, " << QUICKTHREADS_BLOCKI_CR_SAVE << endl; | |
| cout << ".set BLOCK_FSIZE, " << QUICKTHREADS_BLOCK_FRAME_SIZE << endl; | |
| cout << endl; | |
| for(i=0; i<12; i++) | |
| cout << ".set PAR_" << i << ", " << PPC_PAR(i) << endl; | |
| cout << endl; | |
| i = 13; | |
| cout << ".set GPR_SAVE_" << i << ", " << QUICKTHREADS_BLOCKI_GPR_SAVE(i) << endl; | |
| cout << endl; | |
| for(i=31; i>13; i--) | |
| cout << ".set FPR_SAVE_" << i << ", " << QUICKTHREADS_BLOCK_FPR_SAVE(i) << endl; | |
| cout << endl; | |
| cout << ".set VARGS_BKOFF, " << QUICKTHREADS_VARGS_BKOFF << endl; | |
| cout << endl << endl << endl; | |
| for(i=31; i>13; i--) | |
| cout << "\tstfd\tf" << i << ",FPR_SAVE_" << i << "(%r1)" << endl; | |
| cout << endl; | |
| for(i=31; i>13; i--) | |
| cout << "\tlfd \tf" << i << ",FPR_SAVE_" << i << "(%r1)" << endl; | |
| cout << endl << endl << endl; | |
| return 0; | |
| } | |
| * | |
| * | |
| * | |
| */ | |
| #if 0 | |
| .text | |
| .align 4 | |
| .globl qt_block | |
| .globl _qt_block | |
| .globl qt_blocki | |
| .globl _qt_blocki | |
| .globl qt_abort | |
| .globl _qt_abort | |
| .globl qt_start | |
| .globl _qt_start | |
| .globl qt_vstart | |
| .globl _qt_vstart | |
| .set LR_SAVE, 4 | |
| .set BLOCKI_FSIZE, 96 | |
| .set BLOCKI_CR_SAVE, 8 /* CR is saved into the callee's stack frame */ | |
| .set BLOCK_FSIZE, 160 | |
| .set PAR_0, 8 | |
| .set PAR_1, 12 | |
| .set PAR_2, 16 | |
| .set PAR_3, 20 | |
| .set PAR_4, 24 | |
| .set PAR_5, 28 | |
| .set PAR_6, 32 | |
| .set PAR_7, 36 | |
| .set PAR_8, 40 | |
| .set PAR_9, 44 | |
| .set PAR_10, 48 | |
| .set PAR_11, 52 | |
| .set GPR_SAVE_13, 20 | |
| .set FPR_SAVE_31, 152 | |
| .set FPR_SAVE_30, 144 | |
| .set FPR_SAVE_29, 136 | |
| .set FPR_SAVE_28, 128 | |
| .set FPR_SAVE_27, 120 | |
| .set FPR_SAVE_26, 112 | |
| .set FPR_SAVE_25, 104 | |
| .set FPR_SAVE_24, 96 | |
| .set FPR_SAVE_23, 88 | |
| .set FPR_SAVE_22, 80 | |
| .set FPR_SAVE_21, 72 | |
| .set FPR_SAVE_20, 64 | |
| .set FPR_SAVE_19, 56 | |
| .set FPR_SAVE_18, 48 | |
| .set FPR_SAVE_17, 40 | |
| .set FPR_SAVE_16, 32 | |
| .set FPR_SAVE_15, 24 | |
| .set FPR_SAVE_14, 16 | |
| /* various offsets used by "qt_varg" */ | |
| .set P_T, PAR_0 | |
| .set P_STARTUP, PAR_1 | |
| .set P_USERF, PAR_2 | |
| .set P_CLEANUP, PAR_3 | |
| /* the offset used to move back the linkage area to be adiacent to | |
| * the variant argument list before calling "userf(...). | |
| * Skip "t", "startup", "userf", "cleanup" and first | |
| * 8 parameters (since they are passed via registers) */ | |
| .set VARGS_BKOFF, 48 | |
| /* location where "t" and "cleanup" are saved (with respect of | |
| * the stack frame base) */ | |
| .set P_T_SAVE, -4 | |
| .set P_CLEANUP_SAVE, -8 | |
| #endif | |
| /* Block the current thread saving all integer non volatile registers and | |
| * start a new thread. | |
| */ | |
| #if 0 | |
| .if 0 | |
| #endif | |
| void *qt_blocki (void *helper, void *a0, void *a1, void *newthread); | |
| asm void *qt_blocki (void *helper, void *a0, void *a1, void *newthread) | |
| { | |
| #if 0 | |
| .endif | |
| #endif | |
| #if 0 | |
| qt_blocki: | |
| _qt_blocki: | |
| #endif | |
| /* prolog code */ | |
| stwu %r1,-BLOCKI_FSIZE(%r1) /* allocate the stack frame */ | |
| mflr %r0 /* return addr in r0 */ | |
| mfcr %r11 /* CR in r11 */ | |
| stw %r0,LR_SAVE+BLOCKI_FSIZE(%r1) /* save return addr in the stack */ | |
| stw %r11,BLOCKI_CR_SAVE(%r1) /* save CR in the stack */ | |
| stmw %r13,GPR_SAVE_13(%r1) /* save non-volatile reg */ | |
| /* call helper(qt_t *old, void *a0, void *a1) */ | |
| mtlr %r3 /* "helper" addr in the link reg */ | |
| mr %r3,%r1 /* current thread (i.e. the SP) in arg "old" */ | |
| mr %r1,%r6 /* swap to the new thread (i.e. to its SP) */ | |
| blrl /* jump to "helper" */ | |
| /* the "helper" return value is returned (since r3 is not changed) */ | |
| /* epilog code: return to the new thread's "qt_blocki" caller */ | |
| lmw %r13,GPR_SAVE_13(%r1) /* restore non-volatile reg */ | |
| lwz %r0,LR_SAVE+BLOCKI_FSIZE(%r1) /* recover return addr */ | |
| lwz %r11,BLOCKI_CR_SAVE(%r1) /* recover CR */ | |
| mtlr %r0 /* return address in the link reg */ | |
| mtcr %r11 /* restore CR */ | |
| addi %r1,%r1,BLOCKI_FSIZE /* free the stack frame */ | |
| blr /* return */ | |
| #if 0 | |
| .if 0 | |
| #endif | |
| } | |
| #if 0 | |
| .endif | |
| #endif | |
| /* Abort the current thread and start a new thread. | |
| */ | |
| #if 0 | |
| .if 0 | |
| #endif | |
| void qt_abort (void *helper, void *a0, void *a1, void *newthread); | |
| asm void qt_abort (void *helper, void *a0, void *a1, void *newthread) | |
| { | |
| #if 0 | |
| .endif | |
| #endif | |
| #if 0 | |
| qt_abort: | |
| _qt_abort: | |
| #endif | |
| /* prolog code */ | |
| /* there is no prolog. It will never come back */ | |
| /* call helper(qt_t *old, void *a0, void *a1) */ | |
| mtlr %r3 /* "helper" addr in the link reg */ | |
| mr %r1,%r6 /* swap to the new thread (i.e. to its SP) */ | |
| /* we don't need to set "old", we can pass just garbage. Actually, since r3 | |
| is not changed, "old" is set to "helper" (don't care) */ | |
| blrl /* call "helper" */ | |
| /* the "helper" return value is returned (since r3 is not changed) */ | |
| /* epilog code: return to the new thread's "qt_blocki" caller */ | |
| lmw %r13,GPR_SAVE_13(%r1) /* restore non-volatile reg */ | |
| lwz %r0,LR_SAVE+BLOCKI_FSIZE(%r1) /* recover return addr */ | |
| lwz %r11,BLOCKI_CR_SAVE(%r1) /* recover CR */ | |
| mtlr %r0 /* return address in the link reg */ | |
| mtcr %r11 /* restore CR */ | |
| addi %r1,%r1,BLOCKI_FSIZE /* free the stack frame */ | |
| blr /* return */ | |
| #if 0 | |
| .if 0 | |
| #endif | |
| } | |
| #if 0 | |
| .endif | |
| #endif | |
| /* Block the current thread saving all non volatile registers and start | |
| * a new thread. | |
| */ | |
| #if 0 | |
| .if 0 | |
| #endif | |
| void *qt_block (void *helper, void *a0, void *a1, void *newthread); | |
| asm void *qt_block (void *helper, void *a0, void *a1, void *newthread) | |
| { | |
| #if 0 | |
| .endif | |
| #endif | |
| # if 0 | |
| qt_block: | |
| _qt_block: | |
| #endif | |
| /* prolog code */ | |
| stwu %r1,-BLOCK_FSIZE(%r1) /* allocate the stack frame */ | |
| mflr %r0 /* return addr in r0 */ | |
| stw %r0,LR_SAVE+BLOCK_FSIZE(%r1) /* save return addr in the stack */ | |
| /* save non-volatile fp reg */ | |
| stfd %f31,FPR_SAVE_31(%r1) | |
| stfd %f30,FPR_SAVE_30(%r1) | |
| stfd %f29,FPR_SAVE_29(%r1) | |
| stfd %f28,FPR_SAVE_28(%r1) | |
| stfd %f27,FPR_SAVE_27(%r1) | |
| stfd %f26,FPR_SAVE_26(%r1) | |
| stfd %f25,FPR_SAVE_25(%r1) | |
| stfd %f24,FPR_SAVE_24(%r1) | |
| stfd %f23,FPR_SAVE_23(%r1) | |
| stfd %f22,FPR_SAVE_22(%r1) | |
| stfd %f21,FPR_SAVE_21(%r1) | |
| stfd %f20,FPR_SAVE_20(%r1) | |
| stfd %f19,FPR_SAVE_19(%r1) | |
| stfd %f18,FPR_SAVE_18(%r1) | |
| stfd %f17,FPR_SAVE_17(%r1) | |
| stfd %f16,FPR_SAVE_16(%r1) | |
| stfd %f15,FPR_SAVE_15(%r1) | |
| stfd %f14,FPR_SAVE_14(%r1) | |
| /* block the thread */ | |
| bl qt_blocki | |
| /* the thread is going to be resumed */ | |
| /* restore non-volatile fp reg */ | |
| lfd %f31,FPR_SAVE_31(%r1) | |
| lfd %f30,FPR_SAVE_30(%r1) | |
| lfd %f29,FPR_SAVE_29(%r1) | |
| lfd %f28,FPR_SAVE_28(%r1) | |
| lfd %f27,FPR_SAVE_27(%r1) | |
| lfd %f26,FPR_SAVE_26(%r1) | |
| lfd %f25,FPR_SAVE_25(%r1) | |
| lfd %f24,FPR_SAVE_24(%r1) | |
| lfd %f23,FPR_SAVE_23(%r1) | |
| lfd %f22,FPR_SAVE_22(%r1) | |
| lfd %f21,FPR_SAVE_21(%r1) | |
| lfd %f20,FPR_SAVE_20(%r1) | |
| lfd %f19,FPR_SAVE_19(%r1) | |
| lfd %f18,FPR_SAVE_18(%r1) | |
| lfd %f17,FPR_SAVE_17(%r1) | |
| lfd %f16,FPR_SAVE_16(%r1) | |
| lfd %f15,FPR_SAVE_15(%r1) | |
| lfd %f14,FPR_SAVE_14(%r1) | |
| lwz %r0,LR_SAVE+BLOCK_FSIZE(%r1) /* recover return addr */ | |
| mtlr %r0 /* return address in the link reg */ | |
| addi %r1,%r1,BLOCK_FSIZE /* free the stack frame */ | |
| blr /* return */ | |
| #if 0 | |
| .if 0 | |
| #endif | |
| } | |
| #if 0 | |
| .endif | |
| #endif | |
| /* Start a single argument thread using parameters preloaded in the stack | |
| * during thread initialization (see comments on stack initialization in the | |
| * heather file). | |
| * | |
| * Executes: | |
| * | |
| * only(u, t, userf); | |
| */ | |
| #if 0 | |
| .if 0 | |
| #endif | |
| void qt_start(void); | |
| asm void qt_start(void) | |
| { | |
| #if 0 | |
| .endif | |
| #endif | |
| #if 0 | |
| qt_start: | |
| _qt_start: | |
| #endif | |
| lwz %r3,PAR_0(%r1) /* "u" in r3 */ | |
| lwz %r4,PAR_1(%r1) /* "t" in r4 */ | |
| lwz %r5,PAR_2(%r1) /* "userf" in r5 */ | |
| lwz %r6,PAR_3(%r1) /* "only" in r6 */ | |
| mtlr %r6 /* "only" address in the link reg */ | |
| /* call only(u, t, userf) */ | |
| blrl /* jump to "only" */ | |
| /* error if it returns */ | |
| b qt_error | |
| /* dead code (some inline asm "wants" the epilog, or they genetare it) */ | |
| blr | |
| #if 0 | |
| .if 0 | |
| #endif | |
| } | |
| #if 0 | |
| .endif | |
| #endif | |
| /* Start a variant argument thread using parameters preloaded in the stack | |
| * during thread initialization (see comments on stack initialization in the | |
| * heather file). | |
| * | |
| * Executes: | |
| * | |
| * startup(t); | |
| * userf_return = userf(...); | |
| * cleanup(pt, userf_return); | |
| * | |
| ***** Stack layout on start ***** | |
| backchain -> STACK BOTTOM (higher address) | |
| +==========================+ | |
| backchain - 4 -> | | | |
| + LOCAL VARIABLES AREA + | |
| .............. | |
| + + | |
| | | | |
| +--------------------------+ | |
| | | | |
| + ALIGNMEBNT PAD + | |
| .............. | |
| + (if needed) + | |
| | | | |
| +--------------------------+ | |
| | | arg(n) | |
| + + | |
| | | | |
| + VARIABLE ARGUMENT LIST + | |
| .............. | |
| + for userf call + | |
| SP + PAR(5) -> | | arg(1) | |
| + + | |
| SP + PAR(4) -> | | arg(0) | |
| +--------------------------+ | |
| SP + PAR(3) -> | | cleanup par | |
| + + | |
| SP + PAR(2) -> | | userf par | |
| + PARAMETER AREA + | |
| SP + PAR(1) -> | | startup par | |
| + + | |
| SP + PAR(0) -> | | t par | |
| +--------------------------+ | |
| | | | |
| + LINKAGE AREA + | |
| SP -> | | | |
| +==========================+ | |
| STACK TOP (lower address) | |
| Stack grows down | |
| | | |
| V | |
| ***** Stack layout before call userf ***** | |
| backchain -> STACK BOTTOM (higher address) | |
| +==========================+ | |
| backchain - 4 -> | | | |
| + LOCAL VARIABLES AREA + | |
| .............. | |
| + + | |
| | | | |
| +--------------------------+ | |
| | | | |
| + ALIGNMEBNT PAD + | |
| .............. | |
| + (if needed) + | |
| | | | |
| +--------------------------+ | |
| | | arg(n) | |
| + + | |
| | | | |
| + VARIABLE ARGUMENT LIST + | |
| .............. | |
| + for userf call + | |
| SP + PAR(1) -> | | arg(1) | |
| + + | |
| SP + PAR(0) -> | | arg(0) | |
| +--------------------------+ | |
| | | | |
| + LINKAGE AREA + | |
| SP -> | | | |
| +==========================+ | |
| STACK TOP (lower address) | |
| Stack grows down | |
| | | |
| V | |
| * To call "userf(...)", the argument list must be adiacent to the linkage | |
| * area. Instead of copy the argument list, we move back the linkage area | |
| * (actually, we just increase the SP and copy the backchain). "t" and | |
| * "cleanup" are saved in a local variable area in order to call | |
| * cleanup(pt, userf_return). | |
| */ | |
| #if 0 | |
| .if 0 | |
| #endif | |
| void qt_vstart(void); | |
| asm void qt_vstart(void) | |
| { | |
| #if 0 | |
| .endif | |
| #endif | |
| #if 0 | |
| qt_vstart: | |
| _qt_vstart: | |
| #endif | |
| /* NOTICE: the callee routines could save parameter registers in the caller's | |
| * stack parameter area. We put "t" in PAR(0) in such a way, if startup(t) | |
| * will save "t", it will be saved on the same location thus not delething | |
| * any other parameter. | |
| */ | |
| /* since we will move back the linckage area (to make it adiacent to the | |
| * parameter list), we need to save "t" and "cleanup". We have made room for | |
| * this on the bottom of the stack frame. */ | |
| /* save parameters in the local variable area */ | |
| lwz %r11,0(%r1) /* get the backchain */ | |
| lwz %r3,P_T(%r1) | |
| lwz %r4,P_CLEANUP(%r1) | |
| stw %r3,P_T_SAVE(%r11) /* save "pt" */ | |
| stw %r4,P_CLEANUP_SAVE(%r11) /* save "cleanup" */ | |
| /* call startup(t) */ | |
| lwz %r5,P_STARTUP(%r1) | |
| mtlr %r5 | |
| blrl /* call "startup" */ | |
| /* call userf(...) */ | |
| lwz %r11,0(%r1) /* reload backchain (r11 is volatile) */ | |
| lwz %r4,P_USERF(%r1) /* load "userf" */ | |
| mtlr %r4 | |
| /* first eight parameter of the variant list must be copyed in | |
| * GPR3-GPR10. There is a four places offset due to "t", "startup", | |
| * userf" and "cleanup" */ | |
| lwz %r3,PAR_4(%r1) | |
| lwz %r4,PAR_5(%r1) | |
| lwz %r5,PAR_6(%r1) | |
| lwz %r6,PAR_7(%r1) | |
| lwz %r7,PAR_8(%r1) | |
| lwz %r8,PAR_9(%r1) | |
| lwz %r9,PAR_10(%r1) | |
| lwz %r10,PAR_11(%r1) | |
| /* move the linkage area to be adiacent to the argument list */ | |
| stw %r11,VARGS_BKOFF(%r1) /* copy backchain */ | |
| addi %r1,%r1,VARGS_BKOFF /* move back the stack */ | |
| blrl /* call "userf" */ | |
| /* call qt_cleanup(void *pt, void *vuserf_return) */ | |
| lwz %r11,0(%r1) /* reload backchain (r11 is volatile) */ | |
| mr %r4,%r3 /* push "userf" return as 2nd parameter */ | |
| lwz %r3,P_T_SAVE(%r11) /* reload "pt" */ | |
| lwz %r5,P_CLEANUP_SAVE(%r11) /* reload "cleanup" */ | |
| mtlr %r5 | |
| blrl | |
| b qt_error | |
| /* dead code (some inline asm "wants" the epilog, or they genetare it) */ | |
| blr | |
| #if 0 | |
| .if 0 | |
| #endif | |
| } | |
| #if 0 | |
| .endif | |
| #endif | |