q3rally/engine/code/qcommon/vm_powerpc.c
2011-02-18 14:31:32 +00:00

2170 lines
54 KiB
C

/*
===========================================================================
Copyright (C) 2008 Przemyslaw Iskra <sparky@pld-linux.org>
This file is part of Quake III Arena source code.
Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
#include <sys/types.h> /* needed by sys/mman.h on OSX */
#include <sys/mman.h>
#include <sys/time.h>
#include <time.h>
#include <stddef.h>
#ifndef MAP_ANONYMOUS
# define MAP_ANONYMOUS MAP_ANON
#endif
#include "vm_local.h"
#include "vm_powerpc_asm.h"
/*
* VM_TIMES enables showing information about time spent inside
* and outside generated code
*/
//#define VM_TIMES
#ifdef VM_TIMES
#include <sys/times.h>
static clock_t time_outside_vm = 0;
static clock_t time_total_vm = 0;
#endif
/* exit() won't be called but use it because it is marked with noreturn */
#define DIE( reason ) \
do { \
Com_Error(ERR_DROP, "vm_powerpc compiler error: " reason "\n"); \
exit(1); \
} while(0)
/*
* vm_powerpc uses large quantities of memory during compilation,
* Z_Malloc memory may not be enough for some big qvm files
*/
//#define VM_SYSTEM_MALLOC
#ifdef VM_SYSTEM_MALLOC
static inline void *
PPC_Malloc( size_t size )
{
void *mem = malloc( size );
if ( ! mem )
DIE( "Not enough memory" );
return mem;
}
# define PPC_Free free
#else
# define PPC_Malloc Z_Malloc
# define PPC_Free Z_Free
#endif
/*
* optimizations:
* - hole: bubble optimization (OP_CONST+instruction)
* - copy: inline OP_BLOCK_COPY for lengths under 16/32 bytes
* - mask: use rlwinm instruction as dataMask
*/
#ifdef __OPTIMIZE__
# define OPTIMIZE_HOLE 1
# define OPTIMIZE_COPY 1
# define OPTIMIZE_MASK 1
#else
# define OPTIMIZE_HOLE 0
# define OPTIMIZE_COPY 0
# define OPTIMIZE_MASK 0
#endif
/*
* SUPPORTED TARGETS:
* - Linux 32 bits
* ( http://refspecs.freestandards.org/elf/elfspec_ppc.pdf )
* * LR at r0 + 4
* * Local variable space not needed
* -> store caller safe regs at 16+
*
* - Linux 64 bits (not fully conformant)
* ( http://www.ibm.com/developerworks/linux/library/l-powasm4.html )
* * needs "official procedure descriptors" (only first function has one)
* * LR at r0 + 16
* * local variable space required, min 64 bytes, starts at 48
* -> store caller safe regs at 128+
*
* - OS X 32 bits
* ( http://developer.apple.com/documentation/DeveloperTools/Conceptual/LowLevelABI/Articles/32bitPowerPC.html )
* * LR at r0 + 8
* * local variable space required, min 32 bytes (?), starts at 24
* -> store caller safe regs at 64+
*
* - OS X 64 bits (completely untested)
* ( http://developer.apple.com/documentation/DeveloperTools/Conceptual/LowLevelABI/Articles/64bitPowerPC.html )
* * LR at r0 + 16
* * local variable space required, min 64 bytes (?), starts at 48
* -> store caller safe regs at 128+
*/
/* Select Length - first value on 32 bits, second on 64 */
#ifdef __PPC64__
# define SL( a, b ) (b)
#else
# define SL( a, b ) (a)
#endif
/* Select ABI - first for ELF, second for OS X */
#ifdef __ELF__
# define SA( a, b ) (a)
#else
# define SA( a, b ) (b)
#endif
#define ELF32 SL( SA( 1, 0 ), 0 )
#define ELF64 SL( 0, SA( 1, 0 ) )
#define OSX32 SL( SA( 0, 1 ), 0 )
#define OSX64 SL( 0, SA( 0, 1 ) )
/* native length load/store instructions ( L stands for long ) */
#define iSTLU SL( iSTWU, iSTDU )
#define iSTL SL( iSTW, iSTD )
#define iLL SL( iLWZ, iLD )
#define iLLX SL( iLWZX, iLDX )
/* register length */
#define GPRLEN SL( 4, 8 )
#define FPRLEN (8)
/* shift that many bits to obtain value miltiplied by GPRLEN */
#define GPRLEN_SHIFT SL( 2, 3 )
/* Link register position */
#define STACK_LR SL( SA( 4, 8 ), 16 )
/* register save position */
#define STACK_SAVE SL( SA( 16, 64 ), 128 )
/* temporary space, for float<->int exchange */
#define STACK_TEMP SL( SA( 8, 24 ), 48 )
/* red zone temporary space, used instead of STACK_TEMP if stack isn't
* prepared properly */
#define STACK_RTEMP (-16)
#if ELF64
/*
* Official Procedure Descriptor
* we need to prepare one for generated code if we want to call it
* as function
*/
typedef struct {
void *function;
void *toc;
void *env;
} opd_t;
#endif
/*
* opcode information table:
* - length of immediate value
* - returned register type
* - required register(s) type
*/
#define opImm0 0x0000 /* no immediate */
#define opImm1 0x0001 /* 1 byte immadiate value after opcode */
#define opImm4 0x0002 /* 4 bytes immediate value after opcode */
#define opRet0 0x0000 /* returns nothing */
#define opRetI 0x0004 /* returns integer */
#define opRetF 0x0008 /* returns float */
#define opRetIF (opRetI | opRetF) /* returns integer or float */
#define opArg0 0x0000 /* requires nothing */
#define opArgI 0x0010 /* requires integer(s) */
#define opArgF 0x0020 /* requires float(s) */
#define opArgIF (opArgI | opArgF) /* requires integer or float */
#define opArg2I 0x0040 /* requires second argument, integer */
#define opArg2F 0x0080 /* requires second argument, float */
#define opArg2IF (opArg2I | opArg2F) /* requires second argument, integer or float */
static const unsigned char vm_opInfo[256] =
{
[OP_UNDEF] = opImm0,
[OP_IGNORE] = opImm0,
[OP_BREAK] = opImm0,
[OP_ENTER] = opImm4,
/* OP_LEAVE has to accept floats, they will be converted to ints */
[OP_LEAVE] = opImm4 | opRet0 | opArgIF,
/* only STORE4 and POP use values from OP_CALL,
* no need to convert floats back */
[OP_CALL] = opImm0 | opRetI | opArgI,
[OP_PUSH] = opImm0 | opRetIF,
[OP_POP] = opImm0 | opRet0 | opArgIF,
[OP_CONST] = opImm4 | opRetIF,
[OP_LOCAL] = opImm4 | opRetI,
[OP_JUMP] = opImm0 | opRet0 | opArgI,
[OP_EQ] = opImm4 | opRet0 | opArgI | opArg2I,
[OP_NE] = opImm4 | opRet0 | opArgI | opArg2I,
[OP_LTI] = opImm4 | opRet0 | opArgI | opArg2I,
[OP_LEI] = opImm4 | opRet0 | opArgI | opArg2I,
[OP_GTI] = opImm4 | opRet0 | opArgI | opArg2I,
[OP_GEI] = opImm4 | opRet0 | opArgI | opArg2I,
[OP_LTU] = opImm4 | opRet0 | opArgI | opArg2I,
[OP_LEU] = opImm4 | opRet0 | opArgI | opArg2I,
[OP_GTU] = opImm4 | opRet0 | opArgI | opArg2I,
[OP_GEU] = opImm4 | opRet0 | opArgI | opArg2I,
[OP_EQF] = opImm4 | opRet0 | opArgF | opArg2F,
[OP_NEF] = opImm4 | opRet0 | opArgF | opArg2F,
[OP_LTF] = opImm4 | opRet0 | opArgF | opArg2F,
[OP_LEF] = opImm4 | opRet0 | opArgF | opArg2F,
[OP_GTF] = opImm4 | opRet0 | opArgF | opArg2F,
[OP_GEF] = opImm4 | opRet0 | opArgF | opArg2F,
[OP_LOAD1] = opImm0 | opRetI | opArgI,
[OP_LOAD2] = opImm0 | opRetI | opArgI,
[OP_LOAD4] = opImm0 | opRetIF| opArgI,
[OP_STORE1] = opImm0 | opRet0 | opArgI | opArg2I,
[OP_STORE2] = opImm0 | opRet0 | opArgI | opArg2I,
[OP_STORE4] = opImm0 | opRet0 | opArgIF| opArg2I,
[OP_ARG] = opImm1 | opRet0 | opArgIF,
[OP_BLOCK_COPY] = opImm4 | opRet0 | opArgI | opArg2I,
[OP_SEX8] = opImm0 | opRetI | opArgI,
[OP_SEX16] = opImm0 | opRetI | opArgI,
[OP_NEGI] = opImm0 | opRetI | opArgI,
[OP_ADD] = opImm0 | opRetI | opArgI | opArg2I,
[OP_SUB] = opImm0 | opRetI | opArgI | opArg2I,
[OP_DIVI] = opImm0 | opRetI | opArgI | opArg2I,
[OP_DIVU] = opImm0 | opRetI | opArgI | opArg2I,
[OP_MODI] = opImm0 | opRetI | opArgI | opArg2I,
[OP_MODU] = opImm0 | opRetI | opArgI | opArg2I,
[OP_MULI] = opImm0 | opRetI | opArgI | opArg2I,
[OP_MULU] = opImm0 | opRetI | opArgI | opArg2I,
[OP_BAND] = opImm0 | opRetI | opArgI | opArg2I,
[OP_BOR] = opImm0 | opRetI | opArgI | opArg2I,
[OP_BXOR] = opImm0 | opRetI | opArgI | opArg2I,
[OP_BCOM] = opImm0 | opRetI | opArgI,
[OP_LSH] = opImm0 | opRetI | opArgI | opArg2I,
[OP_RSHI] = opImm0 | opRetI | opArgI | opArg2I,
[OP_RSHU] = opImm0 | opRetI | opArgI | opArg2I,
[OP_NEGF] = opImm0 | opRetF | opArgF,
[OP_ADDF] = opImm0 | opRetF | opArgF | opArg2F,
[OP_SUBF] = opImm0 | opRetF | opArgF | opArg2F,
[OP_DIVF] = opImm0 | opRetF | opArgF | opArg2F,
[OP_MULF] = opImm0 | opRetF | opArgF | opArg2F,
[OP_CVIF] = opImm0 | opRetF | opArgI,
[OP_CVFI] = opImm0 | opRetI | opArgF,
};
/*
* source instruction data
*/
typedef struct source_instruction_s source_instruction_t;
struct source_instruction_s {
// opcode
unsigned long int op;
// number of instruction
unsigned long int i_count;
// immediate value (if any)
union {
unsigned int i;
signed int si;
signed short ss[2];
unsigned short us[2];
unsigned char b;
} arg;
// required and returned registers
unsigned char regA1;
unsigned char regA2;
unsigned char regR;
unsigned char regPos;
// next instruction
source_instruction_t *next;
};
/*
* read-only data needed by the generated code
*/
typedef struct VM_Data {
// length of this struct + data
size_t dataLength;
// compiled code size (in bytes)
// it only is code size, without the data
size_t codeLength;
// function pointers, no use to waste registers for them
long int (* AsmCall)( int, int );
void (* BlockCopy )( unsigned int, unsigned int, unsigned int );
// instruction pointers, rarely used so don't waste register
ppc_instruction_t *iPointers;
// data mask for load and store, not used if optimized
unsigned int dataMask;
// fixed number used to convert from integer to float
unsigned int floatBase; // 0x59800004
#if ELF64
// official procedure descriptor
opd_t opd;
#endif
// additional constants, for floating point OP_CONST
// this data has dynamic length, thus '0' here
unsigned int data[0];
} vm_data_t;
#ifdef offsetof
# define VM_Data_Offset( field ) offsetof( vm_data_t, field )
#else
# define OFFSET( structName, field ) \
( (void *)&(((structName *)NULL)->field) - NULL )
# define VM_Data_Offset( field ) OFFSET( vm_data_t, field )
#endif
/*
* functions used by generated code
*/
static long int
VM_AsmCall( int callSyscallInvNum, int callProgramStack )
{
vm_t *savedVM = currentVM;
long int i, ret;
#ifdef VM_TIMES
struct tms start_time, stop_time;
clock_t saved_time = time_outside_vm;
times( &start_time );
#endif
// save the stack to allow recursive VM entry
currentVM->programStack = callProgramStack - 4;
// we need to convert ints to longs on 64bit powerpcs
if ( sizeof( intptr_t ) == sizeof( int ) ) {
intptr_t *argPosition = (intptr_t *)((byte *)currentVM->dataBase + callProgramStack + 4);
// generated code does not invert syscall number
argPosition[ 0 ] = -1 - callSyscallInvNum;
ret = currentVM->systemCall( argPosition );
} else {
intptr_t args[11];
// generated code does not invert syscall number
args[0] = -1 - callSyscallInvNum;
int *argPosition = (int *)((byte *)currentVM->dataBase + callProgramStack + 4);
for( i = 1; i < 11; i++ )
args[ i ] = argPosition[ i ];
ret = currentVM->systemCall( args );
}
currentVM = savedVM;
#ifdef VM_TIMES
times( &stop_time );
time_outside_vm = saved_time + ( stop_time.tms_utime - start_time.tms_utime );
#endif
return ret;
}
static void
VM_BlockCopy( unsigned int dest, unsigned int src, unsigned int count )
{
unsigned dataMask = currentVM->dataMask;
if ( (dest & dataMask) != dest
|| (src & dataMask) != src
|| ((dest+count) & dataMask) != dest + count
|| ((src+count) & dataMask) != src + count)
{
DIE( "OP_BLOCK_COPY out of range!");
}
memcpy( currentVM->dataBase+dest, currentVM->dataBase+src, count );
}
/*
* code-block descriptors
*/
typedef struct dest_instruction dest_instruction_t;
typedef struct symbolic_jump symbolic_jump_t;
struct symbolic_jump {
// number of source instruction it has to jump to
unsigned long int jump_to;
// jump condition true/false, (4*cr7+(eq|gt..))
long int bo, bi;
// extensions / modifiers (branch-link)
unsigned long ext;
// dest_instruction refering to this jump
dest_instruction_t *parent;
// next jump
symbolic_jump_t *nextJump;
};
struct dest_instruction {
// position in the output chain
unsigned long int count;
// source instruction number
unsigned long int i_count;
// exact (for instructins), or maximum (for jump) length
unsigned short length;
dest_instruction_t *next;
// if the instruction is a jump than jump will be non NULL
symbolic_jump_t *jump;
// if jump is NULL than all the instructions will be here
ppc_instruction_t code[0];
};
// first and last instruction,
// di_first is a dummy instruction
static dest_instruction_t *di_first = NULL, *di_last = NULL;
// number of instructions
static unsigned long int di_count = 0;
// pointers needed to compute local jumps, those aren't pointers to
// actual instructions, just used to check how long the jump is going
// to be and whether it is positive or negative
static dest_instruction_t **di_pointers = NULL;
// output instructions which does not come from source code
// use false i_count value
#define FALSE_ICOUNT 0xffffffff
/*
* append specified instructions at the end of instruction chain
*/
static void
PPC_Append(
dest_instruction_t *di_now,
unsigned long int i_count
)
{
di_now->count = di_count++;
di_now->i_count = i_count;
di_now->next = NULL;
di_last->next = di_now;
di_last = di_now;
if ( i_count != FALSE_ICOUNT ) {
if ( ! di_pointers[ i_count ] )
di_pointers[ i_count ] = di_now;
}
}
/*
* make space for instructions and append
*/
static void
PPC_AppendInstructions(
unsigned long int i_count,
size_t num_instructions,
const ppc_instruction_t *is
)
{
if ( num_instructions < 0 )
num_instructions = 0;
size_t iBytes = sizeof( ppc_instruction_t ) * num_instructions;
dest_instruction_t *di_now = PPC_Malloc( sizeof( dest_instruction_t ) + iBytes );
di_now->length = num_instructions;
di_now->jump = NULL;
if ( iBytes > 0 )
memcpy( &(di_now->code[0]), is, iBytes );
PPC_Append( di_now, i_count );
}
/*
* create symbolic jump and append
*/
static symbolic_jump_t *sj_first = NULL, *sj_last = NULL;
static void
PPC_PrepareJump(
unsigned long int i_count,
unsigned long int dest,
long int bo,
long int bi,
unsigned long int ext
)
{
dest_instruction_t *di_now = PPC_Malloc( sizeof( dest_instruction_t ) );
symbolic_jump_t *sj = PPC_Malloc( sizeof( symbolic_jump_t ) );
sj->jump_to = dest;
sj->bo = bo;
sj->bi = bi;
sj->ext = ext;
sj->parent = di_now;
sj->nextJump = NULL;
sj_last->nextJump = sj;
sj_last = sj;
di_now->length = (bo == branchAlways ? 1 : 2);
di_now->jump = sj;
PPC_Append( di_now, i_count );
}
/*
* simplyfy instruction emission
*/
#define emitStart( i_cnt ) \
unsigned long int i_count = i_cnt; \
size_t num_instructions = 0; \
long int force_emit = 0; \
ppc_instruction_t instructions[50];
#define pushIn( inst ) \
(instructions[ num_instructions++ ] = inst)
#define in( inst, args... ) pushIn( IN( inst, args ) )
#define emitEnd() \
do{ \
if ( num_instructions || force_emit ) \
PPC_AppendInstructions( i_count, num_instructions, instructions );\
num_instructions = 0; \
} while(0)
#define emitJump( dest, bo, bi, ext ) \
do { \
emitEnd(); \
PPC_PrepareJump( i_count, dest, bo, bi, ext ); \
} while(0)
/*
* definitions for creating .data section,
* used in cases where constant float is needed
*/
#define LOCAL_DATA_CHUNK 50
typedef struct local_data_s local_data_t;
struct local_data_s {
// number of data in this structure
long int count;
// data placeholder
unsigned int data[ LOCAL_DATA_CHUNK ];
// next chunk, if this one wasn't enough
local_data_t *next;
};
// first data chunk
static local_data_t *data_first = NULL;
// total number of data
static long int data_acc = 0;
/*
* append the data and return its offset
*/
static size_t
PPC_PushData( unsigned int datum )
{
local_data_t *d_now = data_first;
long int accumulated = 0;
// check whether we have this one already
do {
long int i;
for ( i = 0; i < d_now->count; i++ ) {
if ( d_now->data[ i ] == datum ) {
accumulated += i;
return VM_Data_Offset( data[ accumulated ] );
}
}
if ( !d_now->next )
break;
accumulated += d_now->count;
d_now = d_now->next;
} while (1);
// not found, need to append
accumulated += d_now->count;
// last chunk is full, create new one
if ( d_now->count >= LOCAL_DATA_CHUNK ) {
d_now->next = PPC_Malloc( sizeof( local_data_t ) );
d_now = d_now->next;
d_now->count = 0;
d_now->next = NULL;
}
d_now->data[ d_now->count ] = datum;
d_now->count += 1;
data_acc = accumulated + 1;
return VM_Data_Offset( data[ accumulated ] );
}
/*
* find leading zeros in dataMask to implement it with
* "rotate and mask" instruction
*/
static long int fastMaskHi = 0, fastMaskLo = 31;
static void
PPC_MakeFastMask( int mask )
{
#if defined( __GNUC__ ) && ( __GNUC__ >= 4 || ( __GNUC__ == 3 && __GNUC_MINOR__ >= 4 ) )
/* count leading zeros */
fastMaskHi = __builtin_clz( mask );
/* count trailing zeros */
fastMaskLo = 31 - __builtin_ctz( mask );
#else
fastMaskHi = 0;
while ( ( mask & ( 0x80000000 >> fastMaskHi ) ) == 0 )
fastMaskHi++;
fastMaskLo = 31;
while ( ( mask & ( 0x80000000 >> fastMaskLo ) ) == 0 )
fastMaskLo--;
#endif
}
/*
* register definitions
*/
/* registers which are global for generated code */
// pointer to VM_Data (constant)
#define rVMDATA r14
// vm->dataBase (constant)
#define rDATABASE r15
// programStack (variable)
#define rPSTACK r16
/*
* function local registers,
*
* normally only volatile registers are used, but if there aren't enough
* or function has to preserve some value while calling annother one
* then caller safe registers are used as well
*/
static const long int gpr_list[] = {
/* caller safe registers, normally only one is used */
r24, r23, r22, r21,
r20, r19, r18, r17,
/* volatile registers (preferred),
* normally no more than 5 is used */
r3, r4, r5, r6,
r7, r8, r9, r10,
};
static const long int gpr_vstart = 8; /* position of first volatile register */
static const long int gpr_total = sizeof( gpr_list ) / sizeof( gpr_list[0] );
static const long int fpr_list[] = {
/* static registers, normally none is used */
f20, f21, f19, f18,
f17, f16, f15, f14,
/* volatile registers (preferred),
* normally no more than 7 is used */
f0, f1, f2, f3,
f4, f5, f6, f7,
f8, f9, f10, f11,
f12, f13,
};
static const long int fpr_vstart = 8;
static const long int fpr_total = sizeof( fpr_list ) / sizeof( fpr_list[0] );
/*
* prepare some dummy structures and emit init code
*/
static void
PPC_CompileInit( void )
{
di_first = di_last = PPC_Malloc( sizeof( dest_instruction_t ) );
di_first->count = 0;
di_first->next = NULL;
di_first->jump = NULL;
sj_first = sj_last = PPC_Malloc( sizeof( symbolic_jump_t ) );
sj_first->nextJump = NULL;
data_first = PPC_Malloc( sizeof( local_data_t ) );
data_first->count = 0;
data_first->next = NULL;
/*
* init function:
* saves old values of global registers and sets our values
* function prototype is:
* int begin( void *data, int programStack, void *vm->dataBase )
*/
/* first instruction must not be placed on instruction list */
emitStart( FALSE_ICOUNT );
long int stack = STACK_SAVE + 4 * GPRLEN;
in( iMFLR, r0 );
in( iSTLU, r1, -stack, r1 );
in( iSTL, rVMDATA, STACK_SAVE + 0 * GPRLEN, r1 );
in( iSTL, rPSTACK, STACK_SAVE + 1 * GPRLEN, r1 );
in( iSTL, rDATABASE, STACK_SAVE + 2 * GPRLEN, r1 );
in( iSTL, r0, stack + STACK_LR, r1 );
in( iMR, rVMDATA, r3 );
in( iMR, rPSTACK, r4 );
in( iMR, rDATABASE, r5 );
in( iBL, +4*8 ); // LINK JUMP: first generated instruction | XXX jump !
in( iLL, rVMDATA, STACK_SAVE + 0 * GPRLEN, r1 );
in( iLL, rPSTACK, STACK_SAVE + 1 * GPRLEN, r1 );
in( iLL, rDATABASE, STACK_SAVE + 2 * GPRLEN, r1 );
in( iLL, r0, stack + STACK_LR, r1 );
in( iMTLR, r0 );
in( iADDI, r1, r1, stack );
in( iBLR );
emitEnd();
}
// rFIRST is the copy of the top value on the opstack
#define rFIRST (gpr_list[ gpr_pos - 1])
// second value on the opstack
#define rSECOND (gpr_list[ gpr_pos - 2 ])
// temporary registers, not on the opstack
#define rTEMP(x) (gpr_list[ gpr_pos + x ])
#define rTMP rTEMP(0)
#define fFIRST (fpr_list[ fpr_pos - 1 ])
#define fSECOND (fpr_list[ fpr_pos - 2 ])
#define fTEMP(x) (fpr_list[ fpr_pos + x ])
#define fTMP fTEMP(0)
// register types
#define rTYPE_STATIC 0x01
#define rTYPE_FLOAT 0x02
// what type should this opcode return
#define RET_INT ( !(i_now->regR & rTYPE_FLOAT) )
#define RET_FLOAT ( i_now->regR & rTYPE_FLOAT )
// what type should it accept
#define ARG_INT ( ! i_now->regA1 )
#define ARG_FLOAT ( i_now->regA1 )
#define ARG2_INT ( ! i_now->regA2 )
#define ARG2_FLOAT ( i_now->regA2 )
/*
* emit OP_CONST, called if nothing has used the const value directly
*/
static void
PPC_EmitConst( source_instruction_t * const i_const )
{
emitStart( i_const->i_count );
if ( !(i_const->regR & rTYPE_FLOAT) ) {
// gpr_pos needed for "rFIRST" to work
long int gpr_pos = i_const->regPos;
if ( i_const->arg.si >= -0x8000 && i_const->arg.si < 0x8000 ) {
in( iLI, rFIRST, i_const->arg.si );
} else if ( i_const->arg.i < 0x10000 ) {
in( iLI, rFIRST, 0 );
in( iORI, rFIRST, rFIRST, i_const->arg.i );
} else {
in( iLIS, rFIRST, i_const->arg.ss[ 0 ] );
if ( i_const->arg.us[ 1 ] != 0 )
in( iORI, rFIRST, rFIRST, i_const->arg.us[ 1 ] );
}
} else {
// fpr_pos needed for "fFIRST" to work
long int fpr_pos = i_const->regPos;
// there's no good way to generate the data,
// just read it from data section
in( iLFS, fFIRST, PPC_PushData( i_const->arg.i ), rVMDATA );
}
emitEnd();
}
#define MAYBE_EMIT_CONST() if ( i_const ) PPC_EmitConst( i_const )
/*
* emit empty instruction, just sets the needed pointers
*/
static inline void
PPC_EmitNull( source_instruction_t * const i_null )
{
PPC_AppendInstructions( i_null->i_count, 0, NULL );
}
#define EMIT_FALSE_CONST() PPC_EmitNull( i_const )
/*
* analize function for register usage and whether it needs stack (r1) prepared
*/
static void
VM_AnalyzeFunction(
source_instruction_t * const i_first,
long int *prepareStack,
long int *gpr_start_pos,
long int *fpr_start_pos
)
{
source_instruction_t *i_now = i_first;
source_instruction_t *value_provider[20] = { NULL };
unsigned long int opstack_depth = 0;
/*
* first step:
* remember what codes returned some value and mark the value type
* when we get to know what it should be
*/
while ( (i_now = i_now->next) ) {
unsigned long int op = i_now->op;
unsigned long int opi = vm_opInfo[ op ];
if ( opi & opArgIF ) {
assert( opstack_depth > 0 );
opstack_depth--;
source_instruction_t *vp = value_provider[ opstack_depth ];
unsigned long int vpopi = vm_opInfo[ vp->op ];
if ( (opi & opArgI) && (vpopi & opRetI) ) {
// instruction accepts integer, provider returns integer
//vp->regR |= rTYPE_INT;
//i_now->regA1 = rTYPE_INT;
} else if ( (opi & opArgF) && (vpopi & opRetF) ) {
// instruction accepts float, provider returns float
vp->regR |= rTYPE_FLOAT; // use OR here - could be marked as static
i_now->regA1 = rTYPE_FLOAT;
} else {
// instruction arg type does not agree with
// provider return type
DIE( "unrecognized instruction combination" );
}
}
if ( opi & opArg2IF ) {
assert( opstack_depth > 0 );
opstack_depth--;
source_instruction_t *vp = value_provider[ opstack_depth ];
unsigned long int vpopi = vm_opInfo[ vp->op ];
if ( (opi & opArg2I) && (vpopi & opRetI) ) {
// instruction accepts integer, provider returns integer
//vp->regR |= rTYPE_INT;
//i_now->regA2 = rTYPE_INT;
} else if ( (opi & opArg2F) && (vpopi & opRetF) ) {
// instruction accepts float, provider returns float
vp->regR |= rTYPE_FLOAT; // use OR here - could be marked as static
i_now->regA2 = rTYPE_FLOAT;
} else {
// instruction arg type does not agree with
// provider return type
DIE( "unrecognized instruction combination" );
}
}
if (
( op == OP_CALL )
||
( op == OP_BLOCK_COPY && ( i_now->arg.i > SL( 16, 32 ) || !OPTIMIZE_COPY ) )
) {
long int i;
*prepareStack = 1;
// force caller safe registers so we won't have to save them
for ( i = 0; i < opstack_depth; i++ ) {
source_instruction_t *vp = value_provider[ i ];
vp->regR |= rTYPE_STATIC;
}
}
if ( opi & opRetIF ) {
value_provider[ opstack_depth ] = i_now;
opstack_depth++;
}
}
/*
* second step:
* now that we know register types; compute exactly how many registers
* of each type we need
*/
i_now = i_first;
long int needed_reg[4] = {0,0,0,0}, max_reg[4] = {0,0,0,0};
opstack_depth = 0;
while ( (i_now = i_now->next) ) {
unsigned long int op = i_now->op;
unsigned long int opi = vm_opInfo[ op ];
if ( opi & opArgIF ) {
assert( opstack_depth > 0 );
opstack_depth--;
source_instruction_t *vp = value_provider[ opstack_depth ];
needed_reg[ ( vp->regR & 2 ) ] -= 1;
if ( vp->regR & 1 ) // static
needed_reg[ ( vp->regR & 3 ) ] -= 1;
}
if ( opi & opArg2IF ) {
assert( opstack_depth > 0 );
opstack_depth--;
source_instruction_t *vp = value_provider[ opstack_depth ];
needed_reg[ ( vp->regR & 2 ) ] -= 1;
if ( vp->regR & 1 ) // static
needed_reg[ ( vp->regR & 3 ) ] -= 1;
}
if ( opi & opRetIF ) {
long int i;
value_provider[ opstack_depth ] = i_now;
opstack_depth++;
i = i_now->regR & 2;
needed_reg[ i ] += 1;
if ( max_reg[ i ] < needed_reg[ i ] )
max_reg[ i ] = needed_reg[ i ];
i = i_now->regR & 3;
if ( i & 1 ) {
needed_reg[ i ] += 1;
if ( max_reg[ i ] < needed_reg[ i ] )
max_reg[ i ] = needed_reg[ i ];
}
}
}
long int gpr_start = gpr_vstart;
const long int gpr_volatile = gpr_total - gpr_vstart;
if ( max_reg[ 1 ] > 0 || max_reg[ 0 ] > gpr_volatile ) {
// max_reg[ 0 ] - all gprs needed
// max_reg[ 1 ] - static gprs needed
long int max = max_reg[ 0 ] - gpr_volatile;
if ( max_reg[ 1 ] > max )
max = max_reg[ 1 ];
if ( max > gpr_vstart ) {
/* error */
DIE( "Need more GPRs" );
}
gpr_start -= max;
// need stack to save caller safe registers
*prepareStack = 1;
}
*gpr_start_pos = gpr_start;
long int fpr_start = fpr_vstart;
const long int fpr_volatile = fpr_total - fpr_vstart;
if ( max_reg[ 3 ] > 0 || max_reg[ 2 ] > fpr_volatile ) {
// max_reg[ 2 ] - all fprs needed
// max_reg[ 3 ] - static fprs needed
long int max = max_reg[ 2 ] - fpr_volatile;
if ( max_reg[ 3 ] > max )
max = max_reg[ 3 ];
if ( max > fpr_vstart ) {
/* error */
DIE( "Need more FPRs" );
}
fpr_start -= max;
// need stack to save caller safe registers
*prepareStack = 1;
}
*fpr_start_pos = fpr_start;
}
/*
* translate opcodes to ppc instructions,
* it works on functions, not on whole code at once
*/
static void
VM_CompileFunction( source_instruction_t * const i_first )
{
long int prepareStack = 0;
long int gpr_start_pos, fpr_start_pos;
VM_AnalyzeFunction( i_first, &prepareStack, &gpr_start_pos, &fpr_start_pos );
long int gpr_pos = gpr_start_pos, fpr_pos = fpr_start_pos;
// OP_CONST combines well with many opcodes so we treat it in a special way
source_instruction_t *i_const = NULL;
source_instruction_t *i_now = i_first;
// how big the stack has to be
long int save_space = STACK_SAVE;
{
if ( gpr_start_pos < gpr_vstart )
save_space += (gpr_vstart - gpr_start_pos) * GPRLEN;
save_space = ( save_space + 15 ) & ~0x0f;
if ( fpr_start_pos < fpr_vstart )
save_space += (fpr_vstart - fpr_start_pos) * FPRLEN;
save_space = ( save_space + 15 ) & ~0x0f;
}
long int stack_temp = prepareStack ? STACK_TEMP : STACK_RTEMP;
while ( (i_now = i_now->next) ) {
emitStart( i_now->i_count );
switch ( i_now->op )
{
default:
case OP_UNDEF:
case OP_IGNORE:
MAYBE_EMIT_CONST();
in( iNOP );
break;
case OP_BREAK:
MAYBE_EMIT_CONST();
// force SEGV
in( iLWZ, r0, 0, r0 );
break;
case OP_ENTER:
if ( i_const )
DIE( "Weird opcode order" );
// don't prepare stack if not needed
if ( prepareStack ) {
long int i, save_pos = STACK_SAVE;
in( iMFLR, r0 );
in( iSTLU, r1, -save_space, r1 );
in( iSTL, r0, save_space + STACK_LR, r1 );
/* save registers */
for ( i = gpr_start_pos; i < gpr_vstart; i++ ) {
in( iSTL, gpr_list[ i ], save_pos, r1 );
save_pos += GPRLEN;
}
save_pos = ( save_pos + 15 ) & ~0x0f;
for ( i = fpr_start_pos; i < fpr_vstart; i++ ) {
in( iSTFD, fpr_list[ i ], save_pos, r1 );
save_pos += FPRLEN;
}
prepareStack = 2;
}
in( iADDI, rPSTACK, rPSTACK, - i_now->arg.si );
break;
case OP_LEAVE:
if ( i_const ) {
EMIT_FALSE_CONST();
if ( i_const->regR & rTYPE_FLOAT)
DIE( "constant float in OP_LEAVE" );
if ( i_const->arg.si >= -0x8000 && i_const->arg.si < 0x8000 ) {
in( iLI, r3, i_const->arg.si );
} else if ( i_const->arg.i < 0x10000 ) {
in( iLI, r3, 0 );
in( iORI, r3, r3, i_const->arg.i );
} else {
in( iLIS, r3, i_const->arg.ss[ 0 ] );
if ( i_const->arg.us[ 1 ] != 0 )
in( iORI, r3, r3, i_const->arg.us[ 1 ] );
}
gpr_pos--;
} else {
MAYBE_EMIT_CONST();
/* place return value in r3 */
if ( ARG_INT ) {
if ( rFIRST != r3 )
in( iMR, r3, rFIRST );
gpr_pos--;
} else {
in( iSTFS, fFIRST, stack_temp, r1 );
in( iLWZ, r3, stack_temp, r1 );
fpr_pos--;
}
}
// don't undo stack if not prepared
if ( prepareStack >= 2 ) {
long int i, save_pos = STACK_SAVE;
in( iLL, r0, save_space + STACK_LR, r1 );
/* restore registers */
for ( i = gpr_start_pos; i < gpr_vstart; i++ ) {
in( iLL, gpr_list[ i ], save_pos, r1 );
save_pos += GPRLEN;
}
save_pos = ( save_pos + 15 ) & ~0x0f;
for ( i = fpr_start_pos; i < fpr_vstart; i++ ) {
in( iLFD, fpr_list[ i ], save_pos, r1 );
save_pos += FPRLEN;
}
in( iMTLR, r0 );
in( iADDI, r1, r1, save_space );
}
in( iADDI, rPSTACK, rPSTACK, i_now->arg.si);
in( iBLR );
assert( gpr_pos == gpr_start_pos );
assert( fpr_pos == fpr_start_pos );
break;
case OP_CALL:
if ( i_const ) {
EMIT_FALSE_CONST();
if ( i_const->arg.si >= 0 ) {
emitJump(
i_const->arg.i,
branchAlways, 0, branchExtLink
);
} else {
/* syscall */
in( iLL, r0, VM_Data_Offset( AsmCall ), rVMDATA );
in( iLI, r3, i_const->arg.si ); // negative value
in( iMR, r4, rPSTACK ); // push PSTACK on argument list
in( iMTCTR, r0 );
in( iBCTRL );
}
if ( rFIRST != r3 )
in( iMR, rFIRST, r3 );
} else {
MAYBE_EMIT_CONST();
in( iCMPWI, cr7, rFIRST, 0 );
in( iBLTm, cr7, +4*5 /* syscall */ ); // XXX jump !
/* instruction call */
// get instruction address
in( iLL, r0, VM_Data_Offset( iPointers ), rVMDATA );
in( iRLWINM, rFIRST, rFIRST, GPRLEN_SHIFT, 0, 31-GPRLEN_SHIFT ); // mul * GPRLEN
in( iLLX, r0, rFIRST, r0 ); // load pointer
in( iB, +4*(3 + (rFIRST != r3 ? 1 : 0) ) ); // XXX jump !
/* syscall */
in( iLL, r0, VM_Data_Offset( AsmCall ), rVMDATA ); // get asmCall pointer
/* rFIRST can be r3 or some static register */
if ( rFIRST != r3 )
in( iMR, r3, rFIRST ); // push OPSTACK top value on argument list
in( iMR, r4, rPSTACK ); // push PSTACK on argument list
/* common code */
in( iMTCTR, r0 );
in( iBCTRL );
if ( rFIRST != r3 )
in( iMR, rFIRST, r3 ); // push return value on the top of the opstack
}
break;
case OP_PUSH:
MAYBE_EMIT_CONST();
if ( RET_INT )
gpr_pos++;
else
fpr_pos++;
/* no instructions here */
force_emit = 1;
break;
case OP_POP:
MAYBE_EMIT_CONST();
if ( ARG_INT )
gpr_pos--;
else
fpr_pos--;
/* no instructions here */
force_emit = 1;
break;
case OP_CONST:
MAYBE_EMIT_CONST();
/* nothing here */
break;
case OP_LOCAL:
MAYBE_EMIT_CONST();
{
signed long int hi, lo;
hi = i_now->arg.ss[ 0 ];
lo = i_now->arg.ss[ 1 ];
if ( lo < 0 )
hi += 1;
gpr_pos++;
if ( hi == 0 ) {
in( iADDI, rFIRST, rPSTACK, lo );
} else {
in( iADDIS, rFIRST, rPSTACK, hi );
if ( lo != 0 )
in( iADDI, rFIRST, rFIRST, lo );
}
}
break;
case OP_JUMP:
if ( i_const ) {
EMIT_FALSE_CONST();
emitJump(
i_const->arg.i,
branchAlways, 0, 0
);
} else {
MAYBE_EMIT_CONST();
in( iLL, r0, VM_Data_Offset( iPointers ), rVMDATA );
in( iRLWINM, rFIRST, rFIRST, GPRLEN_SHIFT, 0, 31-GPRLEN_SHIFT ); // mul * GPRLEN
in( iLLX, r0, rFIRST, r0 ); // load pointer
in( iMTCTR, r0 );
in( iBCTR );
}
gpr_pos--;
break;
case OP_EQ:
case OP_NE:
if ( i_const && i_const->arg.si >= -0x8000 && i_const->arg.si < 0x10000 ) {
EMIT_FALSE_CONST();
if ( i_const->arg.si >= 0x8000 )
in( iCMPLWI, cr7, rSECOND, i_const->arg.i );
else
in( iCMPWI, cr7, rSECOND, i_const->arg.si );
} else {
MAYBE_EMIT_CONST();
in( iCMPW, cr7, rSECOND, rFIRST );
}
emitJump(
i_now->arg.i,
(i_now->op == OP_EQ ? branchTrue : branchFalse),
4*cr7+eq, 0
);
gpr_pos -= 2;
break;
case OP_LTI:
case OP_GEI:
if ( i_const && i_const->arg.si >= -0x8000 && i_const->arg.si < 0x8000 ) {
EMIT_FALSE_CONST();
in( iCMPWI, cr7, rSECOND, i_const->arg.si );
} else {
MAYBE_EMIT_CONST();
in( iCMPW, cr7, rSECOND, rFIRST );
}
emitJump(
i_now->arg.i,
( i_now->op == OP_LTI ? branchTrue : branchFalse ),
4*cr7+lt, 0
);
gpr_pos -= 2;
break;
case OP_GTI:
case OP_LEI:
if ( i_const && i_const->arg.si >= -0x8000 && i_const->arg.si < 0x8000 ) {
EMIT_FALSE_CONST();
in( iCMPWI, cr7, rSECOND, i_const->arg.si );
} else {
MAYBE_EMIT_CONST();
in( iCMPW, cr7, rSECOND, rFIRST );
}
emitJump(
i_now->arg.i,
( i_now->op == OP_GTI ? branchTrue : branchFalse ),
4*cr7+gt, 0
);
gpr_pos -= 2;
break;
case OP_LTU:
case OP_GEU:
if ( i_const && i_const->arg.i < 0x10000 ) {
EMIT_FALSE_CONST();
in( iCMPLWI, cr7, rSECOND, i_const->arg.i );
} else {
MAYBE_EMIT_CONST();
in( iCMPLW, cr7, rSECOND, rFIRST );
}
emitJump(
i_now->arg.i,
( i_now->op == OP_LTU ? branchTrue : branchFalse ),
4*cr7+lt, 0
);
gpr_pos -= 2;
break;
case OP_GTU:
case OP_LEU:
if ( i_const && i_const->arg.i < 0x10000 ) {
EMIT_FALSE_CONST();
in( iCMPLWI, cr7, rSECOND, i_const->arg.i );
} else {
MAYBE_EMIT_CONST();
in( iCMPLW, cr7, rSECOND, rFIRST );
}
emitJump(
i_now->arg.i,
( i_now->op == OP_GTU ? branchTrue : branchFalse ),
4*cr7+gt, 0
);
gpr_pos -= 2;
break;
case OP_EQF:
case OP_NEF:
MAYBE_EMIT_CONST();
in( iFCMPU, cr7, fSECOND, fFIRST );
emitJump(
i_now->arg.i,
( i_now->op == OP_EQF ? branchTrue : branchFalse ),
4*cr7+eq, 0
);
fpr_pos -= 2;
break;
case OP_LTF:
case OP_GEF:
MAYBE_EMIT_CONST();
in( iFCMPU, cr7, fSECOND, fFIRST );
emitJump(
i_now->arg.i,
( i_now->op == OP_LTF ? branchTrue : branchFalse ),
4*cr7+lt, 0
);
fpr_pos -= 2;
break;
case OP_GTF:
case OP_LEF:
MAYBE_EMIT_CONST();
in( iFCMPU, cr7, fSECOND, fFIRST );
emitJump(
i_now->arg.i,
( i_now->op == OP_GTF ? branchTrue : branchFalse ),
4*cr7+gt, 0
);
fpr_pos -= 2;
break;
case OP_LOAD1:
MAYBE_EMIT_CONST();
#if OPTIMIZE_MASK
in( iRLWINM, rFIRST, rFIRST, 0, fastMaskHi, fastMaskLo );
#else
in( iLWZ, r0, VM_Data_Offset( dataMask ), rVMDATA );
in( iAND, rFIRST, rFIRST, r0 );
#endif
in( iLBZX, rFIRST, rFIRST, rDATABASE );
break;
case OP_LOAD2:
MAYBE_EMIT_CONST();
#if OPTIMIZE_MASK
in( iRLWINM, rFIRST, rFIRST, 0, fastMaskHi, fastMaskLo );
#else
in( iLWZ, r0, VM_Data_Offset( dataMask ), rVMDATA );
in( iAND, rFIRST, rFIRST, r0 );
#endif
in( iLHZX, rFIRST, rFIRST, rDATABASE );
break;
case OP_LOAD4:
MAYBE_EMIT_CONST();
#if OPTIMIZE_MASK
in( iRLWINM, rFIRST, rFIRST, 0, fastMaskHi, fastMaskLo );
#else
in( iLWZ, r0, VM_Data_Offset( dataMask ), rVMDATA );
in( iAND, rFIRST, rFIRST, r0 );
#endif
if ( RET_INT ) {
in( iLWZX, rFIRST, rFIRST, rDATABASE );
} else {
fpr_pos++;
in( iLFSX, fFIRST, rFIRST, rDATABASE );
gpr_pos--;
}
break;
case OP_STORE1:
MAYBE_EMIT_CONST();
#if OPTIMIZE_MASK
in( iRLWINM, rSECOND, rSECOND, 0, fastMaskHi, fastMaskLo );
#else
in( iLWZ, r0, VM_Data_Offset( dataMask ), rVMDATA );
in( iAND, rSECOND, rSECOND, r0 );
#endif
in( iSTBX, rFIRST, rSECOND, rDATABASE );
gpr_pos -= 2;
break;
case OP_STORE2:
MAYBE_EMIT_CONST();
#if OPTIMIZE_MASK
in( iRLWINM, rSECOND, rSECOND, 0, fastMaskHi, fastMaskLo );
#else
in( iLWZ, r0, VM_Data_Offset( dataMask ), rVMDATA );
in( iAND, rSECOND, rSECOND, r0 );
#endif
in( iSTHX, rFIRST, rSECOND, rDATABASE );
gpr_pos -= 2;
break;
case OP_STORE4:
MAYBE_EMIT_CONST();
if ( ARG_INT ) {
#if OPTIMIZE_MASK
in( iRLWINM, rSECOND, rSECOND, 0, fastMaskHi, fastMaskLo );
#else
in( iLWZ, r0, VM_Data_Offset( dataMask ), rVMDATA );
in( iAND, rSECOND, rSECOND, r0 );
#endif
in( iSTWX, rFIRST, rSECOND, rDATABASE );
gpr_pos--;
} else {
#if OPTIMIZE_MASK
in( iRLWINM, rFIRST, rFIRST, 0, fastMaskHi, fastMaskLo );
#else
in( iLWZ, r0, VM_Data_Offset( dataMask ), rVMDATA );
in( iAND, rFIRST, rFIRST, r0 );
#endif
in( iSTFSX, fFIRST, rFIRST, rDATABASE );
fpr_pos--;
}
gpr_pos--;
break;
case OP_ARG:
MAYBE_EMIT_CONST();
in( iADDI, r0, rPSTACK, i_now->arg.b );
if ( ARG_INT ) {
in( iSTWX, rFIRST, rDATABASE, r0 );
gpr_pos--;
} else {
in( iSTFSX, fFIRST, rDATABASE, r0 );
fpr_pos--;
}
break;
case OP_BLOCK_COPY:
MAYBE_EMIT_CONST();
#if OPTIMIZE_COPY
if ( i_now->arg.i <= SL( 16, 32 ) ) {
/* block is very short so copy it in-place */
unsigned int len = i_now->arg.i;
unsigned int copied = 0, left = len;
in( iADD, rFIRST, rFIRST, rDATABASE );
in( iADD, rSECOND, rSECOND, rDATABASE );
if ( len >= GPRLEN ) {
long int i, words = len / GPRLEN;
in( iLL, r0, 0, rFIRST );
for ( i = 1; i < words; i++ )
in( iLL, rTEMP( i - 1 ), GPRLEN * i, rFIRST );
in( iSTL, r0, 0, rSECOND );
for ( i = 1; i < words; i++ )
in( iSTL, rTEMP( i - 1 ), GPRLEN * i, rSECOND );
copied += words * GPRLEN;
left -= copied;
}
if ( SL( 0, left >= 4 ) ) {
in( iLWZ, r0, copied+0, rFIRST );
in( iSTW, r0, copied+0, rSECOND );
copied += 4;
left -= 4;
}
if ( left >= 4 ) {
DIE("Bug in OP_BLOCK_COPY");
}
if ( left == 3 ) {
in( iLHZ, r0, copied+0, rFIRST );
in( iLBZ, rTMP, copied+2, rFIRST );
in( iSTH, r0, copied+0, rSECOND );
in( iSTB, rTMP, copied+2, rSECOND );
} else if ( left == 2 ) {
in( iLHZ, r0, copied+0, rFIRST );
in( iSTH, r0, copied+0, rSECOND );
} else if ( left == 1 ) {
in( iLBZ, r0, copied+0, rFIRST );
in( iSTB, r0, copied+0, rSECOND );
}
} else
#endif
{
unsigned long int r5_ori = 0;
if ( i_now->arg.si >= -0x8000 && i_now->arg.si < 0x8000 ) {
in( iLI, r5, i_now->arg.si );
} else if ( i_now->arg.i < 0x10000 ) {
in( iLI, r5, 0 );
r5_ori = i_now->arg.i;
} else {
in( iLIS, r5, i_now->arg.ss[ 0 ] );
r5_ori = i_now->arg.us[ 1 ];
}
in( iLL, r0, VM_Data_Offset( BlockCopy ), rVMDATA ); // get blockCopy pointer
if ( r5_ori )
in( iORI, r5, r5, r5_ori );
in( iMTCTR, r0 );
if ( rFIRST != r4 )
in( iMR, r4, rFIRST );
if ( rSECOND != r3 )
in( iMR, r3, rSECOND );
in( iBCTRL );
}
gpr_pos -= 2;
break;
case OP_SEX8:
MAYBE_EMIT_CONST();
in( iEXTSB, rFIRST, rFIRST );
break;
case OP_SEX16:
MAYBE_EMIT_CONST();
in( iEXTSH, rFIRST, rFIRST );
break;
case OP_NEGI:
MAYBE_EMIT_CONST();
in( iNEG, rFIRST, rFIRST );
break;
case OP_ADD:
if ( i_const ) {
EMIT_FALSE_CONST();
signed short int hi, lo;
hi = i_const->arg.ss[ 0 ];
lo = i_const->arg.ss[ 1 ];
if ( lo < 0 )
hi += 1;
if ( hi != 0 )
in( iADDIS, rSECOND, rSECOND, hi );
if ( lo != 0 )
in( iADDI, rSECOND, rSECOND, lo );
// if both are zero no instruction will be written
if ( hi == 0 && lo == 0 )
force_emit = 1;
} else {
MAYBE_EMIT_CONST();
in( iADD, rSECOND, rSECOND, rFIRST );
}
gpr_pos--;
break;
case OP_SUB:
MAYBE_EMIT_CONST();
in( iSUB, rSECOND, rSECOND, rFIRST );
gpr_pos--;
break;
case OP_DIVI:
MAYBE_EMIT_CONST();
in( iDIVW, rSECOND, rSECOND, rFIRST );
gpr_pos--;
break;
case OP_DIVU:
MAYBE_EMIT_CONST();
in( iDIVWU, rSECOND, rSECOND, rFIRST );
gpr_pos--;
break;
case OP_MODI:
MAYBE_EMIT_CONST();
in( iDIVW, r0, rSECOND, rFIRST );
in( iMULLW, r0, r0, rFIRST );
in( iSUB, rSECOND, rSECOND, r0 );
gpr_pos--;
break;
case OP_MODU:
MAYBE_EMIT_CONST();
in( iDIVWU, r0, rSECOND, rFIRST );
in( iMULLW, r0, r0, rFIRST );
in( iSUB, rSECOND, rSECOND, r0 );
gpr_pos--;
break;
case OP_MULI:
case OP_MULU:
MAYBE_EMIT_CONST();
in( iMULLW, rSECOND, rSECOND, rFIRST );
gpr_pos--;
break;
case OP_BAND:
MAYBE_EMIT_CONST();
in( iAND, rSECOND, rSECOND, rFIRST );
gpr_pos--;
break;
case OP_BOR:
MAYBE_EMIT_CONST();
in( iOR, rSECOND, rSECOND, rFIRST );
gpr_pos--;
break;
case OP_BXOR:
MAYBE_EMIT_CONST();
in( iXOR, rSECOND, rSECOND, rFIRST );
gpr_pos--;
break;
case OP_BCOM:
MAYBE_EMIT_CONST();
in( iNOT, rFIRST, rFIRST );
break;
case OP_LSH:
MAYBE_EMIT_CONST();
in( iSLW, rSECOND, rSECOND, rFIRST );
gpr_pos--;
break;
case OP_RSHI:
MAYBE_EMIT_CONST();
in( iSRAW, rSECOND, rSECOND, rFIRST );
gpr_pos--;
break;
case OP_RSHU:
MAYBE_EMIT_CONST();
in( iSRW, rSECOND, rSECOND, rFIRST );
gpr_pos--;
break;
case OP_NEGF:
MAYBE_EMIT_CONST();
in( iFNEG, fFIRST, fFIRST );
break;
case OP_ADDF:
MAYBE_EMIT_CONST();
in( iFADDS, fSECOND, fSECOND, fFIRST );
fpr_pos--;
break;
case OP_SUBF:
MAYBE_EMIT_CONST();
in( iFSUBS, fSECOND, fSECOND, fFIRST );
fpr_pos--;
break;
case OP_DIVF:
MAYBE_EMIT_CONST();
in( iFDIVS, fSECOND, fSECOND, fFIRST );
fpr_pos--;
break;
case OP_MULF:
MAYBE_EMIT_CONST();
in( iFMULS, fSECOND, fSECOND, fFIRST );
fpr_pos--;
break;
case OP_CVIF:
MAYBE_EMIT_CONST();
fpr_pos++;
in( iXORIS, rFIRST, rFIRST, 0x8000 );
in( iLIS, r0, 0x4330 );
in( iSTW, rFIRST, stack_temp + 4, r1 );
in( iSTW, r0, stack_temp, r1 );
in( iLFS, fTMP, VM_Data_Offset( floatBase ), rVMDATA );
in( iLFD, fFIRST, stack_temp, r1 );
in( iFSUB, fFIRST, fFIRST, fTMP );
in( iFRSP, fFIRST, fFIRST );
gpr_pos--;
break;
case OP_CVFI:
MAYBE_EMIT_CONST();
gpr_pos++;
in( iFCTIWZ, fFIRST, fFIRST );
in( iSTFD, fFIRST, stack_temp, r1 );
in( iLWZ, rFIRST, stack_temp + 4, r1 );
fpr_pos--;
break;
}
i_const = NULL;
if ( i_now->op != OP_CONST ) {
// emit the instructions if it isn't OP_CONST
emitEnd();
} else {
// mark in what register the value should be saved
if ( RET_INT )
i_now->regPos = ++gpr_pos;
else
i_now->regPos = ++fpr_pos;
#if OPTIMIZE_HOLE
i_const = i_now;
#else
PPC_EmitConst( i_now );
#endif
}
}
if ( i_const )
DIE( "left (unused) OP_CONST" );
{
// free opcode information, don't free first dummy one
source_instruction_t *i_next = i_first->next;
while ( i_next ) {
i_now = i_next;
i_next = i_now->next;
PPC_Free( i_now );
}
}
}
/*
* check which jumps are short enough to use signed 16bit immediate branch
*/
static void
PPC_ShrinkJumps( void )
{
symbolic_jump_t *sj_now = sj_first;
while ( (sj_now = sj_now->nextJump) ) {
if ( sj_now->bo == branchAlways )
// non-conditional branch has 26bit immediate
sj_now->parent->length = 1;
else {
dest_instruction_t *di = di_pointers[ sj_now->jump_to ];
dest_instruction_t *ji = sj_now->parent;
long int jump_length = 0;
if ( ! di )
DIE( "No instruction to jump to" );
if ( ji->count > di->count ) {
do {
jump_length += di->length;
} while ( ( di = di->next ) != ji );
} else {
jump_length = 1;
while ( ( ji = ji->next ) != di )
jump_length += ji->length;
}
if ( jump_length < 0x2000 )
// jump is short, use normal instruction
sj_now->parent->length = 1;
}
}
}
/*
* puts all the data in one place, it consists of many different tasks
*/
static void
PPC_ComputeCode( vm_t *vm )
{
dest_instruction_t *di_now = di_first;
unsigned long int codeInstructions = 0;
// count total instruciton number
while ( (di_now = di_now->next ) )
codeInstructions += di_now->length;
size_t codeLength = sizeof( vm_data_t )
+ sizeof( unsigned int ) * data_acc
+ sizeof( ppc_instruction_t ) * codeInstructions;
// get the memory for the generated code, smarter ppcs need the
// mem to be marked as executable (whill change later)
unsigned char *dataAndCode = mmap( NULL, codeLength,
PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0 );
if ( ! dataAndCode )
DIE( "Not enough memory" );
ppc_instruction_t *codeNow, *codeBegin;
codeNow = codeBegin = (ppc_instruction_t *)( dataAndCode + VM_Data_Offset( data[ data_acc ] ) );
ppc_instruction_t nop = IN( iNOP );
// copy instructions to the destination
// fills the jump instructions with nops
// saves pointers of all instructions
di_now = di_first;
while ( (di_now = di_now->next ) ) {
unsigned long int i_count = di_now->i_count;
if ( i_count != FALSE_ICOUNT ) {
if ( ! di_pointers[ i_count ] )
di_pointers[ i_count ] = (void *) codeNow;
}
if ( di_now->jump == NULL ) {
memcpy( codeNow, &(di_now->code[0]), di_now->length * sizeof( ppc_instruction_t ) );
codeNow += di_now->length;
} else {
long int i;
symbolic_jump_t *sj;
for ( i = 0; i < di_now->length; i++ )
codeNow[ i ] = nop;
codeNow += di_now->length;
sj = di_now->jump;
// save position of jumping instruction
sj->parent = (void *)(codeNow - 1);
}
}
// compute the jumps and write corresponding instructions
symbolic_jump_t *sj_now = sj_first;
while ( (sj_now = sj_now->nextJump ) ) {
ppc_instruction_t *jumpFrom = (void *) sj_now->parent;
ppc_instruction_t *jumpTo = (void *) di_pointers[ sj_now->jump_to ];
signed long int jumpLength = jumpTo - jumpFrom;
// if jump is short, just write it
if ( jumpLength >= - 8192 && jumpLength < 8192 ) {
powerpc_iname_t branchConditional = sj_now->ext & branchExtLink ? iBCL : iBC;
*jumpFrom = IN( branchConditional, sj_now->bo, sj_now->bi, jumpLength * 4 );
continue;
}
// jump isn't short so write it as two instructions
//
// the letter one is a non-conditional branch instruction which
// accepts immediate values big enough (26 bits)
*jumpFrom = IN( (sj_now->ext & branchExtLink ? iBL : iB), jumpLength * 4 );
if ( sj_now->bo == branchAlways )
continue;
// there should have been additional space prepared for this case
if ( jumpFrom[ -1 ] != nop )
DIE( "additional space for long jump not prepared" );
// invert instruction condition
long int bo = 0;
switch ( sj_now->bo ) {
case branchTrue:
bo = branchFalse;
break;
case branchFalse:
bo = branchTrue;
break;
default:
DIE( "unrecognized branch type" );
break;
}
// the former instruction is an inverted conditional branch which
// jumps over the non-conditional one
jumpFrom[ -1 ] = IN( iBC, bo, sj_now->bi, +2*4 );
}
vm->codeBase = dataAndCode;
vm->codeLength = codeLength;
vm_data_t *data = (vm_data_t *)dataAndCode;
#if ELF64
// prepare Official Procedure Descriptor for the generated code
// and retrieve real function pointer for helper functions
opd_t *ac = (void *)VM_AsmCall, *bc = (void *)VM_BlockCopy;
data->opd.function = codeBegin;
// trick it into using the same TOC
// this way we won't have to switch TOC before calling AsmCall or BlockCopy
data->opd.toc = ac->toc;
data->opd.env = ac->env;
data->AsmCall = ac->function;
data->BlockCopy = bc->function;
#else
data->AsmCall = VM_AsmCall;
data->BlockCopy = VM_BlockCopy;
#endif
data->dataMask = vm->dataMask;
data->iPointers = (ppc_instruction_t *)vm->instructionPointers;
data->dataLength = VM_Data_Offset( data[ data_acc ] );
data->codeLength = ( codeNow - codeBegin ) * sizeof( ppc_instruction_t );
data->floatBase = 0x59800004;
/* write dynamic data (float constants) */
{
local_data_t *d_next, *d_now = data_first;
long int accumulated = 0;
do {
long int i;
for ( i = 0; i < d_now->count; i++ )
data->data[ accumulated + i ] = d_now->data[ i ];
accumulated += d_now->count;
d_next = d_now->next;
PPC_Free( d_now );
if ( !d_next )
break;
d_now = d_next;
} while (1);
data_first = NULL;
}
/* free most of the compilation memory */
{
di_now = di_first->next;
PPC_Free( di_first );
PPC_Free( sj_first );
while ( di_now ) {
di_first = di_now->next;
if ( di_now->jump )
PPC_Free( di_now->jump );
PPC_Free( di_now );
di_now = di_first;
}
}
return;
}
static void
VM_Destroy_Compiled( vm_t *self )
{
if ( self->codeBase ) {
if ( munmap( self->codeBase, self->codeLength ) )
Com_Printf( S_COLOR_RED "Memory unmap failed, possible memory leak\n" );
}
self->codeBase = NULL;
}
void
VM_Compile( vm_t *vm, vmHeader_t *header )
{
long int pc = 0;
unsigned long int i_count;
char* code;
struct timeval tvstart = {0, 0};
source_instruction_t *i_first /* dummy */, *i_last = NULL, *i_now;
vm->compiled = qfalse;
gettimeofday(&tvstart, NULL);
PPC_MakeFastMask( vm->dataMask );
i_first = PPC_Malloc( sizeof( source_instruction_t ) );
i_first->next = NULL;
// realloc instructionPointers with correct size
// use Z_Malloc so vm.c will be able to free the memory
if ( sizeof( void * ) != sizeof( int ) ) {
Z_Free( vm->instructionPointers );
vm->instructionPointers = Z_Malloc( header->instructionCount * sizeof( void * ) );
}
di_pointers = (void *)vm->instructionPointers;
memset( di_pointers, 0, header->instructionCount * sizeof( void * ) );
PPC_CompileInit();
/*
* read the input program
* divide it into functions and send each function to compiler
*/
code = (char *)header + header->codeOffset;
for ( i_count = 0; i_count < header->instructionCount; ++i_count )
{
unsigned char op = code[ pc++ ];
if ( op == OP_ENTER ) {
if ( i_first->next )
VM_CompileFunction( i_first );
i_first->next = NULL;
i_last = i_first;
}
i_now = PPC_Malloc( sizeof( source_instruction_t ) );
i_now->op = op;
i_now->i_count = i_count;
i_now->arg.i = 0;
i_now->regA1 = 0;
i_now->regA2 = 0;
i_now->regR = 0;
i_now->regPos = 0;
i_now->next = NULL;
if ( vm_opInfo[op] & opImm4 ) {
union {
unsigned char b[4];
unsigned int i;
} c = { { code[ pc + 3 ], code[ pc + 2 ], code[ pc + 1 ], code[ pc + 0 ] }, };
i_now->arg.i = c.i;
pc += 4;
} else if ( vm_opInfo[op] & opImm1 ) {
i_now->arg.b = code[ pc++ ];
}
i_last->next = i_now;
i_last = i_now;
}
VM_CompileFunction( i_first );
PPC_Free( i_first );
PPC_ShrinkJumps();
memset( di_pointers, 0, header->instructionCount * sizeof( void * ) );
PPC_ComputeCode( vm );
/* check for uninitialized pointers */
#ifdef DEBUG_VM
long int i;
for ( i = 0; i < header->instructionCount; i++ )
if ( di_pointers[ i ] == 0 )
Com_Printf( S_COLOR_RED "Pointer %ld not initialized !\n", i );
#endif
/* mark memory as executable and not writeable */
if ( mprotect( vm->codeBase, vm->codeLength, PROT_READ|PROT_EXEC ) ) {
// it has failed, make sure memory is unmapped before throwing the error
VM_Destroy_Compiled( vm );
DIE( "mprotect failed" );
}
vm->destroy = VM_Destroy_Compiled;
vm->compiled = qtrue;
{
struct timeval tvdone = {0, 0};
struct timeval dur = {0, 0};
Com_Printf( "VM file %s compiled to %i bytes of code (%p - %p)\n",
vm->name, vm->codeLength, vm->codeBase, vm->codeBase+vm->codeLength );
gettimeofday(&tvdone, NULL);
timersub(&tvdone, &tvstart, &dur);
Com_Printf( "compilation took %lu.%06lu seconds\n",
(long unsigned int)dur.tv_sec, (long unsigned int)dur.tv_usec );
}
}
int
VM_CallCompiled( vm_t *vm, int *args )
{
int retVal;
int *argPointer;
vm_data_t *vm_dataAndCode = (void *)( vm->codeBase );
int programStack = vm->programStack;
int stackOnEntry = programStack;
byte *image = vm->dataBase;
currentVM = vm;
vm->currentlyInterpreting = qtrue;
programStack -= 48;
argPointer = (int *)&image[ programStack + 8 ];
memcpy( argPointer, args, 4 * 9 );
argPointer[ -1 ] = 0;
argPointer[ -2 ] = -1;
#ifdef VM_TIMES
struct tms start_time, stop_time;
clock_t time_diff;
times( &start_time );
time_outside_vm = 0;
#endif
/* call generated code */
{
int ( *entry )( void *, int, void * );
#ifdef __PPC64__
entry = (void *)&(vm_dataAndCode->opd);
#else
entry = (void *)(vm->codeBase + vm_dataAndCode->dataLength);
#endif
retVal = entry( vm->codeBase, programStack, vm->dataBase );
}
#ifdef VM_TIMES
times( &stop_time );
time_diff = stop_time.tms_utime - start_time.tms_utime;
time_total_vm += time_diff - time_outside_vm;
if ( time_diff > 100 ) {
printf( "App clock: %ld, vm total: %ld, vm this: %ld, vm real: %ld, vm out: %ld\n"
"Inside VM %f%% of app time\n",
stop_time.tms_utime,
time_total_vm,
time_diff,
time_diff - time_outside_vm,
time_outside_vm,
(double)100 * time_total_vm / stop_time.tms_utime );
}
#endif
vm->programStack = stackOnEntry;
vm->currentlyInterpreting = qfalse;
return retVal;
}