bugfixes, test_misc2, checkpc options

git-svn-id: file:///home/notaz/opt/svn/PicoDrive@181 be3aeb3a-fb24-0410-a615-afba39da0efa
This commit is contained in:
notaz 2007-07-05 20:25:06 +00:00
parent 85a36a57a8
commit 03c5768cb5
8 changed files with 267 additions and 103 deletions

View file

@ -14,11 +14,11 @@ ___________________________________________________________________________
Cyclone 68000 (c) Copyright 2004 Dave. Free for non-commercial use
Homepage: http://www.finalburn.com/
Dave's e-mail: dev(atsymbol)finalburn.com
Dave's e-mail: emudave(atsymbol)googlemail.com
Replace (atsymbol) with @
Additional coding and bugfixes done by notaz, 2005, 2006
Homepage: http://mif.vu.lt/~grig2790/Cyclone/
Additional coding and bugfixes done by notaz, 2005-2007
Homepage: http://mif.vu.lt/~grig2790/Cyclone/ , http://notaz.gp2x.de
e-mail: notasas(atsymbol)gmail.com
___________________________________________________________________________
@ -35,6 +35,17 @@ Flags are mapped onto ARM flags whenever possible, which speeds up the processin
What's New
----------
v0.0087 notaz
- Reduced amount of code in opcode handlers by ~23% by doing the following:
- Removed duplicate opcode handlers
- Optimized code to use less ARM instructions
- Merged some duplicate handler endings
+ Cyclone now does better job avoiding pipeline interlocks.
+ Replaced incorrect handler of DBT with proper one.
+ Fixed "MOVEA (An)+ An" behaviour.
+ Fixed flags for ROXR and LSR. Hopefully got them right now.
+ Additional functionality added for MAME and other ports (see config.h).
v0.0086 notaz
+ Cyclone now can be customized to better suit your project, see config.h .
+ Added an option to compress the jumptable at compile-time. Must call CycloneInit()

View file

@ -98,11 +98,12 @@ static int EaCalcReg(int r,int ea,int mask,int forceor,int shift,int noshift=0)
for (i=mask|0x8000; (i&1)==0; i>>=1) low++; // Find out how high up the EA mask is
mask&=0xf<<low; // This is the max we can do
if (ea>=8) needor=1; // Need to OR to access A0-7
if (((mask&g_op)>>low)&8) needor=0; // Ah - no we don't actually need to or, since the bit is high in r8
if (forceor) needor=1; // Special case for 0x30-0x38 EAs ;)
if (ea>=8)
{
needor=1; // Need to OR to access A0-7
if ((g_op>>low)&8) { needor=0; mask|=8<<low; } // Ah - no we don't actually need to or, since the bit is high in r8
if (forceor) needor=1; // Special case for 0x30-0x38 EAs ;)
}
ot(" and r%d,r8,#0x%.4x\n",r,mask);
if (needor) ot(" orr r%d,r%d,#0x%x ;@ A0-7\n",r,r,8<<low);

View file

@ -25,20 +25,21 @@ int OpArith(int op)
OpStart(op, sea, tea); Cycles=4;
EaCalcReadNoSE((type!=6)?11:-1,0,tea,size,0x003f);
// imm must be read first
EaCalcReadNoSE(-1,10,sea,size,0);
EaCalcReadNoSE((type!=6)?11:-1,0,tea,size,0x003f);
if (size<2) shiftstr=(char *)(size?",asl #16":",asl #24");
if (size<2) ot(" mov r0,r0,asl %i\n",size?16:24);
if (size<2) ot(" mov r10,r10,asl #%i\n",size?16:24);
ot(";@ Do arithmetic:\n");
if (type==0) ot(" orr r1,r0,r10%s\n",shiftstr);
if (type==1) ot(" and r1,r0,r10%s\n",shiftstr);
if (type==2) ot(" subs r1,r0,r10%s ;@ Defines NZCV\n",shiftstr);
if (type==3) ot(" adds r1,r0,r10%s ;@ Defines NZCV\n",shiftstr);
if (type==5) ot(" eor r1,r0,r10%s\n",shiftstr);
if (type==6) ot(" cmp r0,r10%s ;@ Defines NZCV\n",shiftstr);
if (type==0) ot(" orr r1,r10,r0%s\n",shiftstr);
if (type==1) ot(" and r1,r10,r0%s\n",shiftstr);
if (type==2||type==6)
ot(" rsbs r1,r10,r0%s ;@ Defines NZCV\n",shiftstr);
if (type==3) ot(" adds r1,r10,r0%s ;@ Defines NZCV\n",shiftstr);
if (type==5) ot(" eor r1,r10,r0%s\n",shiftstr);
if (type<2 || type==5) ot(" adds r1,r1,#0 ;@ Defines NZ, clears CV\n"); // 0,1,5
@ -384,7 +385,7 @@ int OpAbcd(int op)
ot(";@ Get src/dest EA vals\n");
EaCalc (0,0x000f, sea,0,1);
EaRead (0, 10, sea,0,0x000f,1);
EaCalcReadNoSE(11,0,dea,0,0x1e00);
EaCalcReadNoSE(11,0,dea,0,0x0e00);
}
else
{
@ -540,13 +541,11 @@ int OpAritha(int op)
if(size==2&&(sea<0x10||sea==0x3c)) Cycles+=2;
if(type==1) Cycles=6;
// must calculate reg EA first, because of situations like: suba.w (A0)+, A0
EaCalc (10,0x1e00, dea,2,1);
EaRead (10, 11, dea,2,0x1e00);
EaCalc ( 0,0x003f, sea,size,1);
EaRead ( 0, 0, sea,size,0x003f,1);
// to handle suba.w (A0)+, A0 properly, must calc reg EA first
EaCalcReadNoSE(type!=1?10:-1,11,dea,2,0x0e00);
EaCalcReadNoSE(-1,0,sea,size,0x003f);
if (size<2) ot(" mov r0,r0,asl #%d\n\n",size?16:24);
if (size<2) asr=(char *)(size?",asr #16":",asr #24");
if (type==0) ot(" sub r11,r11,r0%s\n",asr);
@ -555,7 +554,7 @@ int OpAritha(int op)
if (type==2) ot(" add r11,r11,r0%s\n",asr);
ot("\n");
if (type!=1) EaWrite(10, 11, dea,2,0x0e00,1);
if (type!=1) EaWrite(10, 11, dea,2,0x0e00);
OpEnd(sea);
@ -596,7 +595,7 @@ int OpAddx(int op)
ot(";@ Get src/dest EA vals\n");
EaCalc (0,0x000f, sea,size,1);
EaRead (0, 11, sea,size,0x000f,1);
EaCalcReadNoSE(10,0,dea,size,0x1e00);
EaCalcReadNoSE(10,0,dea,size,0x0e00);
}
else
{
@ -716,11 +715,11 @@ int OpCmpm(int op)
OpStart(op,sea); Cycles=4;
ot(";@ Get src operand into r10:\n");
EaCalc (0,0x1e00, sea,size,1);
EaRead (0, 10, sea,size,0x000f,1);
EaCalc (0,0x0007, sea,size,1);
EaRead (0, 10, sea,size,0x0007,1);
ot(";@ Get dst operand into r0:\n");
EaCalcReadNoSE(-1,0,dea,size,0x1e00);
EaCalcReadNoSE(-1,0,dea,size,0x0e00);
if (size<2) asl=(char *)(size?",asl #16":",asl #24");

View file

@ -158,11 +158,12 @@ int OpUnlk(int op)
}
// --------------------- Opcodes 0x4e70+ ---------------------
// 01001110 01110ttt
int Op4E70(int op)
{
int type=0;
type=op&7; // 01001110 01110ttt, reset/nop/stop/rte/rtd/rts/trapv/rtr
type=op&7; // reset/nop/stop/rte/rtd/rts/trapv/rtr
switch (type)
{
@ -236,7 +237,7 @@ int OpJsr(int op)
ot(";@ Jump - Get new PC from r0\n");
if (op&0x40)
{
// Jmp - Get new PC from r0
// Jmp - Get new PC from r11
ot(" add r0,r11,r10 ;@ Memory Base + New PC\n");
ot("\n");
}
@ -319,14 +320,24 @@ int OpDbra(int op)
ot(";@ Check if Dn.w is -1\n");
ot(" cmn r0,#1\n");
#if USE_CHECKPC_CALLBACK && USE_CHECKPC_DBRA
ot(" beq DbraMin1\n");
ot("\n");
ot(";@ Get Branch offset:\n");
ot(" ldrsh r0,[r4]\n");
ot(" add r0,r4,r0 ;@ r4 = New PC\n");
CheckPc(0);
#else
ot("\n");
ot(";@ Get Branch offset:\n");
ot(" ldrnesh r0,[r4]\n");
ot(" addeq r4,r4,#2 ;@ Skip branch offset\n");
ot(" subeq r5,r5,#4 ;@ additional cycles\n");
ot(" addne r4,r4,r0 ;@ r4 = New PC\n");
ot("\n");
#endif
Cycles=12-2;
OpEnd();
}
@ -342,6 +353,18 @@ int OpDbra(int op)
OpEnd();
}
#if USE_CHECKPC_CALLBACK && USE_CHECKPC_DBRA
if (op==0x51c8)
{
ot(";@ Dn.w is -1:\n");
ot("DbraMin1%s\n", ms?"":":");
ot(" add r4,r4,#2 ;@ Skip branch offset\n");
ot("\n");
Cycles=12+2;
OpEnd();
}
#endif
return 0;
}
@ -349,7 +372,7 @@ int OpDbra(int op)
// Emit a Branch opcode 0110cccc nn (cccc=condition)
int OpBranch(int op)
{
int size=0,use=0;
int size=0,use=0,checkpc=0;
int offset=0;
int cc=0;
char *asr_r11="";
@ -426,28 +449,26 @@ int OpBranch(int op)
MemHandler(1,2);
ot("\n");
Cycles=18; // always 18
if (offset==0 || offset==-1)
{
ot(";@ Branch is quite far, so may be a good idea to check Memory Base+pc\n");
ot(" add r0,r4,r11%s ;@ r4 = New PC\n",asr_r11);
CheckPc(0);
}
else
ot(" add r4,r4,r11%s ;@ r4 = New PC\n",asr_r11);
}
#if USE_CHECKPC_CALLBACK && USE_CHECKPC_OFFSETBITS_8
if (offset!=0 && offset!=-1) checkpc=1;
#endif
#if USE_CHECKPC_CALLBACK && USE_CHECKPC_OFFSETBITS_16
if (offset==0) checkpc=1;
#endif
#if USE_CHECKPC_CALLBACK
if (offset==-1) checkpc=1;
#endif
if (checkpc)
{
ot(" add r0,r4,r11%s ;@ r4 = New PC\n",asr_r11);
CheckPc(0);
}
else
{
if (offset==0 || offset==-1)
{
ot(" add r0,r4,r11%s ;@ r4 = New PC\n",asr_r11);
ot(";@ Branch is quite far, so may be a good idea to check Memory Base+pc\n");
CheckPc(0);
}
else
{
ot(" add r4,r4,r11%s ;@ r4 = New PC\n",asr_r11);
ot("\n");
}
ot(" add r4,r4,r11%s ;@ r4 = New PC\n",asr_r11);
ot("\n");
}
OpEnd(size?0x10:0);
@ -456,7 +477,7 @@ int OpBranch(int op)
if (cc>=2&&(op&0xff00)==0x6200)
{
ot("BccDontBranch%i%s\n", 8<<size, ms?"":":");
if (size) ot(" add r4,r4,#%d\n",8<<size);
if (size) ot(" add r4,r4,#%d\n",1<<size);
Cycles+=(size==1) ? 2 : -2; // Branch not taken
OpEnd(0);
}

View file

@ -409,13 +409,6 @@ static int EmitAsr(int op,int type,int dir,int count,int size,int usereg)
if (type==0) ot(" movs r0,r0,%s %s\n",dir?"asl":"asr",pct);
if (type==1) ot(" movs r0,r0,%s %s\n",dir?"lsl":"lsr",pct);
if (dir==0 && size<2)
{
ot(";@ restore after right shift:\n");
ot(" mov r0,r0,lsl #%d\n",32-(8<<size));
ot("\n");
}
OpGetFlags(0,0);
if (usereg) { // store X only if count is not 0
ot(" cmp %s,#0 ;@ shifting by 0?\n",pct);
@ -425,6 +418,16 @@ static int EmitAsr(int op,int type,int dir,int count,int size,int usereg)
// count will never be 0 if we use immediate
ot(" str r9,[r7,#0x4c] ;@ Save X bit\n");
}
ot("\n");
if (dir==0 && size<2)
{
ot(";@ restore after right shift:\n");
ot(" movs r0,r0,lsl #%d\n",32-(8<<size));
if (type)
ot(" orrmi r9,r9,#0x80000000 ;@ Potentially missed N flag\n");
ot("\n");
}
if (type==0 && dir) {
ot(";@ calculate V flag (set if sign bit changes at anytime):\n");
@ -433,9 +436,8 @@ static int EmitAsr(int op,int type,int dir,int count,int size,int usereg)
ot(" cmpne r3,r1,asr %s\n", pct);
ot(" biceq r9,r9,#0x10000000\n");
ot(" orrne r9,r9,#0x10000000\n");
ot("\n");
}
ot("\n");
}
// --------------------------------------
@ -467,12 +469,20 @@ static int EmitAsr(int op,int type,int dir,int count,int size,int usereg)
if (usereg)
{
ot(";@ Reduce r2 until <0:\n");
ot("Reduce_%.4x%s\n",op,ms?"":":");
ot(" subs r2,r2,#%d\n",wide+1);
ot(" bpl Reduce_%.4x\n",op);
ot(" adds r2,r2,#%d ;@ Now r2=0-%d\n",wide+1,wide);
ot(" beq norotx%.4x\n",op);
if (size==2)
{
ot(" subs r2,r2,#33\n");
ot(" addmis r2,r2,#33 ;@ Now r2=0-%d\n",wide);
}
else
{
ot(";@ Reduce r2 until <0:\n");
ot("Reduce_%.4x%s\n",op,ms?"":":");
ot(" subs r2,r2,#%d\n",wide+1);
ot(" bpl Reduce_%.4x\n",op);
ot(" adds r2,r2,#%d ;@ Now r2=0-%d\n",wide+1,wide);
}
ot(" beq norotx_%.4x\n",op);
ot("\n");
}
@ -488,30 +498,26 @@ static int EmitAsr(int op,int type,int dir,int count,int size,int usereg)
if (shift) ot(" mov r0,r0,lsr #%d ;@ Shift down\n",shift);
ot(";@ Rotate bits:\n");
ot(" mov r3,r0,lsr r2 ;@ Get right part\n");
ot(" rsbs r2,r2,#%d ;@ should also clear ARM V\n",wide+1);
ot(" movs r0,r0,lsl r2 ;@ Get left part\n");
ot(" orr r0,r3,r0 ;@ r0=Rotated value\n");
ot(";@ Insert X bit into r2-1:\n");
ot("\n");
ot(";@ First get X bit (middle):\n");
ot(" ldr r3,[r7,#0x4c]\n");
ot(" sub r2,r2,#1\n");
ot(" rsb r1,r2,#%d\n",wide);
ot(" and r3,r3,#0x20000000\n");
ot(" mov r3,r3,lsr #29\n");
ot(" orr r0,r0,r3,lsl r2\n");
ot(" mov r3,r3,lsl r1\n");
ot(";@ Rotate bits:\n");
ot(" orr r3,r3,r0,lsr r2 ;@ Orr right part\n");
ot(" rsbs r2,r2,#%d ;@ should also clear ARM V\n",wide+1);
ot(" orrs r0,r3,r0,lsl r2 ;@ Orr left part, set flags\n");
ot("\n");
if (shift) ot(" movs r0,r0,lsl #%d ;@ Shift up and get correct NC flags\n",shift);
OpGetFlags(0,!usereg);
if (!shift) {
ot(" tst r0,r0\n");
ot(" bicne r9,r9,#0x40000000 ;@ make sure we didn't mess Z\n");
}
if (usereg) { // store X only if count is not 0
ot(" str r9,[r7,#0x4c] ;@ if not 0, Save X bit\n");
ot(" b nozerox%.4x\n",op);
ot("norotx%.4x%s\n",op,ms?"":":");
ot("norotx_%.4x%s\n",op,ms?"":":");
ot(" ldr r2,[r7,#0x4c]\n");
ot(" adds r0,r0,#0 ;@ Defines NZ, clears CV\n");
OpGetFlags(0,0);
@ -536,9 +542,10 @@ static int EmitAsr(int op,int type,int dir,int count,int size,int usereg)
}
ot(";@ Rotate register:\n");
if (!dir) ot(" adds r0,r0,#0 ;@ first clear V and C\n"); // ARM does not clear C if rot count is 0
if (count<0)
{
if (dir) ot(" rsbs %s,%s,#32\n",pct,pct);
if (dir) ot(" rsb %s,%s,#32\n",pct,pct);
ot(" movs r0,r0,ror %s\n",pct);
}
else
@ -549,9 +556,9 @@ static int EmitAsr(int op,int type,int dir,int count,int size,int usereg)
}
OpGetFlags(0,0);
if (!dir) ot(" bic r9,r9,#0x10000000 ;@ make suve V is clear\n");
if (dir)
{
ot(" bic r9,r9,#0x30000000 ;@ clear CV\n");
ot(";@ Get carry bit from bit 0:\n");
if (usereg)
{
@ -561,14 +568,6 @@ static int EmitAsr(int op,int type,int dir,int count,int size,int usereg)
else
ot(" tst r0,#1\n");
ot(" orrne r9,r9,#0x20000000\n");
ot(" biceq r9,r9,#0x20000000\n");
}
else if (usereg)
{
// if we rotate something by 0, ARM doesn't clear C
// so we need to detect that
ot(" cmp %s,#0\n",pct);
ot(" biceq r9,r9,#0x20000000\n");
}
ot("\n");
@ -599,7 +598,7 @@ int OpAsr(int op)
// Use the same opcode for target registers:
use=op&~0x0007;
// As long as count is not 8, use the same opcode for all shift counts::
// As long as count is not 8, use the same opcode for all shift counts:
if (usereg==0 && count!=8 && !(count==1&&type==2)) { use|=0x0e00; count=-1; }
if (usereg) { use&=~0x0e00; count=-1; } // Use same opcode for all Dn

View file

@ -133,18 +133,18 @@ int OpMove(int op)
if (movea) size=2; // movea always expands to 32-bits
#if SPLIT_MOVEL_PD
EaCalc (10,0x1e00,tea,size,0,0);
EaCalc (10,0x0e00,tea,size,0,0);
if ((tea&0x38)==0x20 && size==2) { // -(An)
ot(" mov r11,r1\n");
ot(" add r0,r10,#2\n");
EaWrite(0, 1,tea,1,0x1e00,0,0);
EaWrite(10, 11,tea,1,0x1e00,1);
EaWrite(0, 1,tea,1,0x0e00,0,0);
EaWrite(10, 11,tea,1,0x0e00,1);
} else {
EaWrite(0, 1,tea,size,0x1e00,0,0);
EaWrite(0, 1,tea,size,0x0e00,0,0);
}
#else
EaCalc (0,0x1e00,tea,size,0,0);
EaWrite(0, 1,tea,size,0x1e00,0,0);
EaCalc (0,0x0e00,tea,size,0,0);
EaWrite(0, 1,tea,size,0x0e00,0,0);
#endif
#if CYCLONE_FOR_GENESIS && !MEMHANDLERS_CHANGE_CYCLES
@ -178,8 +178,8 @@ int OpLea(int op)
OpStart(op,sea,tea);
EaCalc (1,0x003f,sea,0); // Lea
EaCalc (0,0x1e00,tea,2);
EaWrite(0, 1,tea,2,0x1e00);
EaCalc (0,0x0e00,tea,2);
EaWrite(0, 1,tea,2,0x0e00);
Cycles=Ea_add_ns(g_lea_cycle_table,sea);
@ -513,7 +513,7 @@ int OpMovep(int op)
// Get EA
ea = (op&0x0007)|0x28;
rea= op&0x0e00;
rea= (op&0x0e00)>>9;
dir = (op>>7)&1;
// Find size extension
@ -522,7 +522,7 @@ int OpMovep(int op)
OpStart(op,ea);
if(dir) { // reg to mem
EaCalcReadNoSE(-1,11,rea,size,0x1e00);
EaCalcReadNoSE(-1,11,rea,size,0x0e00);
EaCalc(10,0x000f,ea,size);
if(size==2) { // if operand is long

View file

@ -27,7 +27,7 @@
* Warning: if you enable this, you MUST call CycloneInit() before calling
* CycloneRun(), or else it will crash.
*/
#define COMPRESS_JUMPTABLE 1
#define COMPRESS_JUMPTABLE 0
/*
* Address mask for memory hadlers. The bits set will be masked out of address
@ -107,10 +107,23 @@
*/
#define USE_CHECKPC_CALLBACK 1
/*
* This determines if checkpc() should be called after jumps when 8 and 16 bit
* displacement values were used.
*/
#define USE_CHECKPC_OFFSETBITS_16 1
#define USE_CHECKPC_OFFSETBITS_8 0
/*
* Call checkpc() after DBcc jumps (which use 16bit displacement). Cyclone prior to
* 0.0087 never did that.
*/
#define USE_CHECKPC_DBRA 0
/*
* When this option is enabled Cyclone will do two word writes instead of one
* long write when handling MOVE.L with pre-decrementing destination, as described in
* Bart Trzynadlowski's doc (http://www.trzy.org/files/68knotes.txt).
* Enable this if you are emulating a 16 bit system.
*/
#define SPLIT_MOVEL_PD 1
#define SPLIT_MOVEL_PD 0

View file

@ -0,0 +1,120 @@
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
static FILE *f;
#define bswap16(x) (x=(unsigned short)((x<<8)|(x>>8)))
#define bswap32(x) (x=((x<<24)|((x<<8)&0xff0000)|((x>>8)&0x00ff00)|((unsigned)x>>24)))
static void write_op(unsigned short op, unsigned short word0, unsigned short word1, unsigned short word2)
{
bswap16(op);
bswap16(word0);
bswap16(word1);
bswap16(word2);
fwrite(&op, 1, sizeof(op), f);
fwrite(&word0, 1, sizeof(word0), f);
fwrite(&word1, 1, sizeof(word1), f);
fwrite(&word2, 1, sizeof(word2), f);
}
static void write32(unsigned int a)
{
bswap32(a);
fwrite(&a, 1, sizeof(a), f);
}
static int op_check(unsigned short op)
{
if ((op&0xf000) == 0x6000) return 0; // Bxx
if ((op&0xf0f8) == 0x50c8) return 0; // DBxx
if ((op&0xff80) == 0x4e80) return 0; // Jsr
if ((op&0xf000) == 0xa000) return 0; // a-line
if ((op&0xf000) == 0xf000) return 0; // f-line
if ((op&0xfff8)==0x4e70&&op!=0x4e71&&op!=0x4e76) return 0; // reset, rte, rts
if ((op&0x3f) >= 0x28) op = (op&~0x3f) | (rand() % 0x28);
return 1;
}
static unsigned short safe_rand(void)
{
unsigned short op;
/* avoid branch opcodes */
do
{
op = rand();
}
while (!op_check(op));
return op;
}
int main()
{
int i, op;
srand(time(0));
f = fopen("test_misc2.bin", "wb");
if (!f) return 1;
write32(0x00ff8000); // stack
write32(0x300); // IP
for (i=0x100/4-2; i; i--)
{
write32(0x200+i*4); // exception vectors
}
for (i=0x100/4; i; i--)
{
write32(0); // pad
}
for (i=0x100/4; i; i--)
{
write32(0x4e734e73); // fill with rte instructions
}
for (op = 0; op < 0x10000; op++)
{
if ((op&0xf000) == 0x6000) // Bxx
{
if ((op&0x00ff) == 0)
write_op(op, 6, 0, 0);
else if ((op&0x00ff) == 0xff)
write_op(op, 0, 6, 0);
}
else if ((op&0xf0f8)==0x50c8) // DBxx
{
write_op(op, 6, 0, 0);
}
else if ((op&0xff80)==0x4e80) // Jsr
{
int addr = 0x300 + i*8 + 8;
if ((op&0x3f) == 0x39)
write_op(op, addr >> 16, addr & 0xffff, 0);
}
else if ((op&0xf000)==0xa000 || (op&0xf000)==0xf000) // a-line, f-line
{
if (op != 0xa000 && op != 0xf000) continue;
}
else if ((op&0xfff8)==0x4e70&&op!=0x4e71&&op!=0x4e76); // rte, rts, stop, reset
else
{
write_op(op, safe_rand(), safe_rand(), safe_rand());
}
}
// jump to the beginning
write_op(0x4ef8, 0x300, 0, 0);
fclose(f);
return 0;
}