{ $Id$ Copyright (c) 1998-2000 by Florian Klaempfl Generate i386 inline nodes This program 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. This program 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 this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. **************************************************************************** } unit n386inl; {$i defines.inc} interface uses node,ninl; type ti386inlinenode = class(tinlinenode) procedure pass_2;override; end; implementation uses globtype,systems, cutils,verbose,globals,fmodule, symconst,symtype,symdef,aasm,types, cgbase,temp_gen,pass_1,pass_2, cpubase, nbas,ncon,ncal,ncnv,nld, cgobj,cga,tgcpu,n386util,ncgutil; {***************************************************************************** TI386INLINENODE *****************************************************************************} procedure ti386inlinenode.pass_2; const {tfloattype = (s32real,s64real,s80real,s64bit,f16bit,f32bit);} { float_name: array[tfloattype] of string[8]= ('S32REAL','S64REAL','S80REAL','S64BIT','F16BIT','F32BIT'); } incdecop:array[in_inc_x..in_dec_x] of tasmop=(A_INC,A_DEC); addsubop:array[in_inc_x..in_dec_x] of tasmop=(A_ADD,A_SUB); var opsize : topsize; op, asmop : tasmop; pushed : tpushed; {inc/dec} addconstant : boolean; addvalue : longint; hp : tnode; var r : preference; //hp : tcallparanode; hp2 : tstringconstnode; dummycoll : tparaitem; l : longint; ispushed : boolean; hregister : tregister; lengthlab, otlabel,oflabel{,l1} : tasmlabel; oldpushedparasize : longint; def : tdef; hr,hr2 : treference; begin { save & reset pushedparasize } oldpushedparasize:=pushedparasize; pushedparasize:=0; case inlinenumber of in_assert_x_y: begin { the node should be removed in the firstpass } if not (cs_do_assertion in aktlocalswitches) then internalerror(7123458); otlabel:=truelabel; oflabel:=falselabel; getlabel(truelabel); getlabel(falselabel); secondpass(tcallparanode(left).left); maketojumpbool(tcallparanode(left).left,lr_load_regvars); emitlab(falselabel); { erroraddr } emit_reg(A_PUSH,S_L,R_EBP); { lineno } emit_const(A_PUSH,S_L,aktfilepos.line); { filename string } hp2:=cstringconstnode.createstr(current_module.sourcefiles.get_file_name(aktfilepos.fileindex),st_shortstring); firstpass(hp2); secondpass(hp2); if codegenerror then exit; emitpushreferenceaddr(hp2.location.reference); hp2.free; { push msg } secondpass(tcallparanode(tcallparanode(left).right).left); emitpushreferenceaddr(tcallparanode(tcallparanode(left).right).left.location.reference); { call } emitcall('FPC_ASSERT'); emitlab(truelabel); truelabel:=otlabel; falselabel:=oflabel; end; in_sizeof_x, in_typeof_x : begin { for both cases load vmt } if left.nodetype=typen then begin location.register:=getregisterint; emit_sym_ofs_reg(A_MOV, S_L,newasmsymbol(tobjectdef(left.resulttype.def).vmt_mangledname),0, location.register); end else begin secondpass(left); del_reference(left.location.reference); location.loc:=LOC_REGISTER; location.register:=getregisterint; { load VMT pointer } inc(left.location.reference.offset, tobjectdef(left.resulttype.def).vmt_offset); emit_ref_reg(A_MOV,S_L, newreference(left.location.reference), location.register); end; { in sizeof load size } if inlinenumber=in_sizeof_x then begin new(r); reset_reference(r^); r^.base:=location.register; emit_ref_reg(A_MOV,S_L,r, location.register); end; end; in_length_x : begin secondpass(left); set_location(location,left.location); { length in ansi strings is at offset -8 } if is_ansistring(left.resulttype.def) or is_widestring(left.resulttype.def) then begin if left.location.loc<>LOC_REGISTER then begin del_location(left.location); hregister:=getregisterint; emit_mov_loc_reg(left.location,hregister); end else hregister:=left.location.register; reset_reference(hr); hr.base:=hregister; hr.offset:=-8; getlabel(lengthlab); emit_reg_reg(A_OR,S_L,hregister,hregister); emitjmp(C_Z,lengthlab); emit_ref_reg(A_MOV,S_L,newreference(hr),hregister); emitlab(lengthlab); location.loc:=LOC_REGISTER; location.register:=hregister; end; end; in_pred_x, in_succ_x: begin secondpass(left); if not (cs_check_overflow in aktlocalswitches) then if inlinenumber=in_pred_x then asmop:=A_DEC else asmop:=A_INC else if inlinenumber=in_pred_x then asmop:=A_SUB else asmop:=A_ADD; case resulttype.def.size of 8 : opsize:=S_L; 4 : opsize:=S_L; 2 : opsize:=S_W; 1 : opsize:=S_B; else internalerror(10080); end; location.loc:=LOC_REGISTER; if resulttype.def.size=8 then begin if left.location.loc<>LOC_REGISTER then begin if left.location.loc=LOC_CREGISTER then begin location.registerlow:=getregisterint; location.registerhigh:=getregisterint; emit_reg_reg(A_MOV,opsize,left.location.registerlow, location.registerlow); emit_reg_reg(A_MOV,opsize,left.location.registerhigh, location.registerhigh); end else begin del_reference(left.location.reference); location.registerlow:=getregisterint; location.registerhigh:=getregisterint; emit_ref_reg(A_MOV,opsize,newreference(left.location.reference), location.registerlow); r:=newreference(left.location.reference); inc(r^.offset,4); emit_ref_reg(A_MOV,opsize,r, location.registerhigh); end; end else begin location.registerhigh:=left.location.registerhigh; location.registerlow:=left.location.registerlow; end; if inlinenumber=in_succ_x then begin emit_const_reg(A_ADD,opsize,1, location.registerlow); emit_const_reg(A_ADC,opsize,0, location.registerhigh); end else begin emit_const_reg(A_SUB,opsize,1, location.registerlow); emit_const_reg(A_SBB,opsize,0, location.registerhigh); end; end else begin if left.location.loc<>LOC_REGISTER then begin { first, we've to release the source location ... } if left.location.loc in [LOC_MEM,LOC_REFERENCE] then del_reference(left.location.reference); location.register:=getregisterint; if (resulttype.def.size=2) then location.register:=reg32toreg16(location.register); if (resulttype.def.size=1) then location.register:=reg32toreg8(location.register); if left.location.loc=LOC_CREGISTER then emit_reg_reg(A_MOV,opsize,left.location.register, location.register) else if left.location.loc=LOC_FLAGS then emit_flag2reg(left.location.resflags,location.register) else emit_ref_reg(A_MOV,opsize,newreference(left.location.reference), location.register); end else location.register:=left.location.register; if not (cs_check_overflow in aktlocalswitches) then emit_reg(asmop,opsize, location.register) else emit_const_reg(asmop,opsize,1, location.register); end; emitoverflowcheck(self); cg.g_rangecheck(exprasmlist,self,resulttype.def); end; in_dec_x, in_inc_x : begin { set defaults } addvalue:=1; addconstant:=true; { load first parameter, must be a reference } secondpass(tcallparanode(left).left); case tcallparanode(left).left.resulttype.def.deftype of orddef, enumdef : begin case tcallparanode(left).left.resulttype.def.size of 1 : opsize:=S_B; 2 : opsize:=S_W; 4 : opsize:=S_L; 8 : opsize:=S_L; end; end; pointerdef : begin opsize:=S_L; if is_void(tpointerdef(tcallparanode(left).left.resulttype.def).pointertype.def) then addvalue:=1 else addvalue:=tpointerdef(tcallparanode(left).left.resulttype.def).pointertype.def.size; end; else internalerror(10081); end; { second argument specified?, must be a s32bit in register } if assigned(tcallparanode(left).right) then begin ispushed:=maybe_push(tcallparanode(tcallparanode(left).right).left.registers32, tcallparanode(left).left,false); secondpass(tcallparanode(tcallparanode(left).right).left); if ispushed then restore(tcallparanode(left).left,false); { when constant, just multiply the addvalue } if is_constintnode(tcallparanode(tcallparanode(left).right).left) then addvalue:=addvalue*get_ordinal_value(tcallparanode(tcallparanode(left).right).left) else begin case tcallparanode(tcallparanode(left).right).left.location.loc of LOC_REGISTER, LOC_CREGISTER : hregister:=tcallparanode(tcallparanode(left).right).left.location.register; LOC_MEM, LOC_REFERENCE : begin del_reference(tcallparanode(tcallparanode(left).right).left.location.reference); hregister:=getregisterint; emit_ref_reg(A_MOV,S_L, newreference(tcallparanode(tcallparanode(left).right).left.location.reference),hregister); end; else internalerror(10082); end; { insert multiply with addvalue if its >1 } if addvalue>1 then emit_const_reg(A_IMUL,opsize, addvalue,hregister); addconstant:=false; end; end; { write the add instruction } if addconstant then begin if (addvalue=1) and not(cs_check_overflow in aktlocalswitches) then begin if tcallparanode(left).left.location.loc=LOC_CREGISTER then emit_reg(incdecop[inlinenumber],opsize, tcallparanode(left).left.location.register) else emit_ref(incdecop[inlinenumber],opsize, newreference(tcallparanode(left).left.location.reference)) end else begin if tcallparanode(left).left.location.loc=LOC_CREGISTER then emit_const_reg(addsubop[inlinenumber],opsize, addvalue,tcallparanode(left).left.location.register) else emit_const_ref(addsubop[inlinenumber],opsize, addvalue,newreference(tcallparanode(left).left.location.reference)); end end else begin { BUG HERE : detected with nasm : hregister is allways 32 bit it should be converted to 16 or 8 bit depending on op_size PM } { still not perfect : if hregister is already a 16 bit reg ?? PM } { makeregXX is the solution (FK) } case opsize of S_B : hregister:=makereg8(hregister); S_W : hregister:=makereg16(hregister); end; if tcallparanode(left).left.location.loc=LOC_CREGISTER then emit_reg_reg(addsubop[inlinenumber],opsize, hregister,tcallparanode(left).left.location.register) else emit_reg_ref(addsubop[inlinenumber],opsize, hregister,newreference(tcallparanode(left).left.location.reference)); case opsize of S_B : hregister:=reg8toreg32(hregister); S_W : hregister:=reg16toreg32(hregister); end; ungetregister32(hregister); end; emitoverflowcheck(tcallparanode(left).left); cg.g_rangecheck(exprasmlist,tcallparanode(left).left,tcallparanode(left).left.resulttype.def); end; in_typeinfo_x: begin location.register:=getregisterint; new(r); reset_reference(r^); r^.symbol:=tstoreddef(ttypenode(tcallparanode(left).left).resulttype.def).get_rtti_label(fullrtti); emit_ref_reg(A_LEA,S_L,r,location.register); end; in_finalize_x: begin pushusedregisters(pushed,$ff); { if a count is passed, push size, typeinfo and count } if assigned(tcallparanode(left).right) then begin secondpass(tcallparanode(tcallparanode(left).right).left); push_int(tcallparanode(left).left.resulttype.def.size); if codegenerror then exit; emit_push_loc(tcallparanode(tcallparanode(left).right).left.location); end; { generate a reference } reset_reference(hr); hr.symbol:=tstoreddef(ttypenode(tcallparanode(left).left).resulttype.def).get_rtti_label(initrtti); emitpushreferenceaddr(hr); { data to finalize } secondpass(tcallparanode(left).left); if codegenerror then exit; emitpushreferenceaddr(tcallparanode(left).left.location.reference); saveregvars($ff); if assigned(tcallparanode(left).right) then emitcall('FPC_FINALIZEARRAY') else emitcall('FPC_FINALIZE'); popusedregisters(pushed); end; in_assigned_x : begin secondpass(tcallparanode(left).left); location.loc:=LOC_FLAGS; if (tcallparanode(left).left.location.loc in [LOC_REGISTER,LOC_CREGISTER]) then begin emit_reg_reg(A_OR,S_L, tcallparanode(left).left.location.register, tcallparanode(left).left.location.register); ungetregister32(tcallparanode(left).left.location.register); end else begin emit_const_ref(A_CMP,S_L,0, newreference(tcallparanode(left).left.location.reference)); del_reference(tcallparanode(left).left.location.reference); end; location.resflags:=F_NE; end; in_setlength_x: begin pushusedregisters(pushed,$ff); l:=0; { push dimensions } hp:=left; while assigned(tcallparanode(hp).right) do begin inc(l); hp:=tcallparanode(hp).right; end; def:=tcallparanode(hp).left.resulttype.def; hp:=left; if is_dynamic_array(def) then begin { get temp. space } gettempofsizereference(l*4,hr); { keep data start } hr2:=hr; { copy dimensions } hp:=left; while assigned(tcallparanode(hp).right) do begin secondpass(tcallparanode(hp).left); emit_mov_loc_ref(tcallparanode(hp).left.location,hr, S_L,true); inc(hr.offset,4); hp:=tcallparanode(hp).right; end; end else begin secondpass(tcallparanode(hp).left); emit_push_loc(tcallparanode(hp).left.location); hp:=tcallparanode(hp).right; end; { handle shortstrings separately since the hightree must be } { pushed too (JM) } if not(is_dynamic_array(def)) and (tstringdef(def).string_typ = st_shortstring) then begin dummycoll:=TParaItem.Create; dummycoll.paratyp:=vs_var; dummycoll.paratype:=openshortstringtype; tcallparanode(hp).secondcallparan(dummycoll,false,false,false,0,0); dummycoll.free; if codegenerror then exit; end else secondpass(tcallparanode(hp).left); if is_dynamic_array(def) then begin emitpushreferenceaddr(hr2); push_int(l); reset_reference(hr2); hr2.symbol:=tstoreddef(def).get_rtti_label(initrtti); emitpushreferenceaddr(hr2); emitpushreferenceaddr(tcallparanode(hp).left.location.reference); saveregvars($ff); emitcall('FPC_DYNARR_SETLENGTH'); ungetiftemp(hr); end else { must be string } begin case tstringdef(def).string_typ of st_widestring: begin emitpushreferenceaddr(tcallparanode(hp).left.location.reference); saveregvars($ff); emitcall('FPC_WIDESTR_SETLENGTH'); end; st_ansistring: begin emitpushreferenceaddr(tcallparanode(hp).left.location.reference); saveregvars($ff); emitcall('FPC_ANSISTR_SETLENGTH'); end; st_shortstring: begin saveregvars($ff); emitcall('FPC_SHORTSTR_SETLENGTH'); end; end; end; popusedregisters(pushed); maybe_loadself; end; in_include_x_y, in_exclude_x_y: begin secondpass(tcallparanode(left).left); if tcallparanode(tcallparanode(left).right).left.nodetype=ordconstn then begin { calculate bit position } l:=1 shl (tordconstnode(tcallparanode(tcallparanode(left).right).left).value mod 32); { determine operator } if inlinenumber=in_include_x_y then asmop:=A_OR else begin asmop:=A_AND; l:=not(l); end; if (tcallparanode(left).left.location.loc=LOC_REFERENCE) then begin inc(tcallparanode(left).left.location.reference.offset, (tordconstnode(tcallparanode(tcallparanode(left).right).left).value div 32)*4); emit_const_ref(asmop,S_L, l,newreference(tcallparanode(left).left.location.reference)); del_reference(tcallparanode(left).left.location.reference); end else { LOC_CREGISTER } begin secondpass(tcallparanode(left).left); emit_const_reg(asmop,S_L, l,tcallparanode(left).left.location.register); end; end else begin { generate code for the element to set } ispushed:=maybe_push(tcallparanode(tcallparanode(left).right).left.registers32, tcallparanode(left).left,false); secondpass(tcallparanode(tcallparanode(left).right).left); if ispushed then restore(tcallparanode(left).left,false); { determine asm operator } if inlinenumber=in_include_x_y then asmop:=A_BTS else asmop:=A_BTR; if tcallparanode(tcallparanode(left).right).left.location.loc in [LOC_CREGISTER,LOC_REGISTER] then { we don't need a mod 32 because this is done automatically } { by the bts instruction. For proper checking we would } { note: bts doesn't do any mod'ing, that's why we can also use } { it for normalsets! (JM) } { need a cmp and jmp, but this should be done by the } { type cast code which does range checking if necessary (FK) } begin hregister := tcallparanode(tcallparanode(left).right).left.location.register; emit_to_reg32(hregister); end else begin getexplicitregister32(R_EDI); hregister:=R_EDI; opsize:=def2def_opsize( tcallparanode(tcallparanode(left).right).left.resulttype.def,u32bittype.def); if opsize = S_L then op:=A_MOV else op:=A_MOVZX; emit_ref_reg(op,opsize, newreference( tcallparanode(tcallparanode(left).right).left.location.reference),R_EDI); end; if (tcallparanode(left).left.location.loc=LOC_REFERENCE) then emit_reg_ref(asmop,S_L,hregister, newreference(tcallparanode(left).left.location.reference)) else emit_reg_reg(asmop,S_L,hregister, tcallparanode(left).left.location.register); if hregister = R_EDI then ungetregister32(R_EDI); end; end; in_pi: begin emit_none(A_FLDPI,S_NO); inc(fpuvaroffset); end; in_sin_extended, in_arctan_extended, in_abs_extended, in_sqr_extended, in_sqrt_extended, in_ln_extended, in_cos_extended: begin secondpass(left); case left.location.loc of LOC_FPU: ; LOC_CFPUREGISTER: begin emit_reg(A_FLD,S_NO, correct_fpuregister(left.location.register,fpuvaroffset)); inc(fpuvaroffset); end; LOC_REFERENCE,LOC_MEM: begin floatload(tfloatdef(left.resulttype.def).typ,left.location.reference); del_reference(left.location.reference); end else internalerror(309991); end; case inlinenumber of in_sin_extended, in_cos_extended: begin if inlinenumber=in_sin_extended then emit_none(A_FSIN,S_NO) else emit_none(A_FCOS,S_NO); { getlabel(l1); emit_reg(A_FNSTSW,S_NO,R_AX); emit_none(A_SAHF,S_NO); emitjmp(C_NP,l1); emit_reg(A_FSTP,S_NO,R_ST0); emit_none(A_FLDZ,S_NO); emitlab(l1); } end; in_arctan_extended: begin emit_none(A_FLD1,S_NO); emit_none(A_FPATAN,S_NO); end; in_abs_extended: emit_none(A_FABS,S_NO); in_sqr_extended: begin (* emit_reg(A_FLD,S_NO,R_ST0); { emit_none(A_FMULP,S_NO); nasm does not accept this PM } emit_reg_reg(A_FMULP,S_NO,R_ST0,R_ST1); can be shorten to *) emit_reg_reg(A_FMUL,S_NO,R_ST0,R_ST0); end; in_sqrt_extended: emit_none(A_FSQRT,S_NO); in_ln_extended: begin emit_none(A_FLDLN2,S_NO); emit_none(A_FXCH,S_NO); emit_none(A_FYL2X,S_NO); end; end; end; {$ifdef SUPPORT_MMX} in_mmx_pcmpeqb..in_mmx_pcmpgtw: begin if left.location.loc=LOC_REGISTER then begin {!!!!!!!} end else if tcallparanode(left).left.location.loc=LOC_REGISTER then begin {!!!!!!!} end else begin {!!!!!!!} end; end; {$endif SUPPORT_MMX} else internalerror(9); end; { reset pushedparasize } pushedparasize:=oldpushedparasize; end; begin cinlinenode:=ti386inlinenode; end. { $Log$ Revision 1.31 2001-12-30 17:24:46 jonas * range checking is now processor independent (part in cgobj, part in cg64f32) and should work correctly again (it needed some changes after the changes of the low and high of tordef's to int64) * maketojumpbool() is now processor independent (in ncgutil) * getregister32 is now called getregisterint Revision 1.30 2001/12/10 14:34:04 jonas * fixed type conversions from dynamic arrays to open arrays Revision 1.29 2001/12/04 15:59:03 jonas * converted lo/hi to processor independent code, generated code is the same as before (when turning on the optimizer) Revision 1.28 2001/12/02 16:19:17 jonas * less unnecessary regvar loading with if-statements Revision 1.26 2001/09/28 20:38:51 jonas * fixed big bug in my previous changes (the arguent for bts/btr is always a 32 bit register, but it wasn't cleared properly if the value was only an 8 bit one) Revision 1.25 2001/09/27 13:03:18 jonas * fixed bug reported by sg about self not being restored after calling setlength Revision 1.24 2001/09/04 14:32:45 jonas * simplified det_resulttype code for include/exclude * include/exclude doesn't use any helpers anymore in the i386 secondpass Revision 1.23 2001/08/30 20:13:57 peter * rtti/init table updates * rttisym for reusable global rtti/init info * support published for interfaces Revision 1.22 2001/08/28 13:24:47 jonas + compilerproc implementation of most string-related type conversions - removed all code from the compiler which has been replaced by compilerproc implementations (using ($ifdef hascompilerproc) is not necessary in the compiler) Revision 1.21 2001/08/26 13:36:58 florian * some cg reorganisation * some PPC updates Revision 1.20 2001/08/24 12:33:54 jonas * fixed big bug in handle_str that caused it to (almost) always call fpc__longint * fixed small bug in handle_read_write that caused wrong warnigns about uninitialized vars with read(ln) + handle_val (processor independent val() handling) Revision 1.19 2001/08/23 14:28:36 jonas + tempcreate/ref/delete nodes (allows the use of temps in the resulttype and first pass) * made handling of read(ln)/write(ln) processor independent * moved processor independent handling for str and reset/rewrite-typed from firstpass to resulttype pass * changed names of helpers in text.inc to be generic for use as compilerprocs + added "iocheck" directive for most of them * reading of ordinals is done by procedures instead of functions because otherwise FPC_IOCHECK overwrote the result before it could be stored elsewhere (range checking still works) * compilerprocs can now be used in the system unit before they are implemented * added note to errore.msg that booleans can't be read using read/readln Revision 1.18 2001/08/13 15:39:52 jonas * made in_reset_typedfile/in_rewrite_typedfile handling processor independent Revision 1.17 2001/08/13 12:41:57 jonas * made code for str(x,y) completely processor independent Revision 1.16 2001/07/10 18:01:08 peter * internal length for ansistring and widestrings Revision 1.15 2001/07/08 21:00:18 peter * various widestring updates, it works now mostly without charset mapping supported Revision 1.14 2001/04/13 01:22:19 peter * symtable change to classes * range check generation and errors fixed, make cycle DEBUG=1 works * memory leaks fixed Revision 1.13 2001/04/02 21:20:37 peter * resulttype rewrite Revision 1.12 2001/03/13 11:52:48 jonas * fixed some memory leaks Revision 1.11 2000/12/25 00:07:33 peter + new tlinkedlist class (merge of old tstringqueue,tcontainer and tlinkedlist objects) Revision 1.10 2000/12/09 22:51:37 florian * helper name of val for qword fixed Revision 1.9 2000/12/07 17:19:46 jonas * new constant handling: from now on, hex constants >$7fffffff are parsed as unsigned constants (otherwise, $80000000 got sign extended and became $ffffffff80000000), all constants in the longint range become longints, all constants >$7fffffff and <=cardinal($ffffffff) are cardinals and the rest are int64's. * added lots of longint typecast to prevent range check errors in the compiler and rtl * type casts of symbolic ordinal constants are now preserved * fixed bug where the original resulttype.def wasn't restored correctly after doing a 64bit rangecheck Revision 1.8 2000/12/05 11:44:33 jonas + new integer regvar handling, should be much more efficient Revision 1.7 2000/11/29 00:30:47 florian * unused units removed from uses clause * some changes for widestrings Revision 1.6 2000/11/12 23:24:15 florian * interfaces are basically running Revision 1.5 2000/11/09 17:46:56 florian * System.TypeInfo fixed + System.Finalize implemented + some new keywords for interface support added Revision 1.4 2000/10/31 22:02:56 peter * symtable splitted, no real code changes Revision 1.3 2000/10/26 14:15:07 jonas * fixed setlength for shortstrings Revision 1.2 2000/10/21 18:16:13 florian * a lot of changes: - basic dyn. array support - basic C++ support - some work for interfaces done .... Revision 1.1 2000/10/15 09:33:31 peter * moved n386*.pas to i386/ cpu_target dir Revision 1.2 2000/10/15 09:08:58 peter * use System for the systemunit instead of target dependent Revision 1.1 2000/10/14 10:14:49 peter * moehrendorf oct 2000 rewrite }