From: Julian Seward Date: Wed, 6 Apr 2005 20:01:56 +0000 (+0000) Subject: More AMD64 instructions: sfence, movnti, bsf{w,l,q} X-Git-Tag: svn/VALGRIND_3_0_1^2~209 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=693e9ca6a52cf3721f4cf6b8116471e106425193;p=thirdparty%2Fvalgrind.git More AMD64 instructions: sfence, movnti, bsf{w,l,q} git-svn-id: svn://svn.valgrind.org/vex/trunk@1125 --- diff --git a/VEX/priv/guest-amd64/toIR.c b/VEX/priv/guest-amd64/toIR.c index 6024348f99..e26efec052 100644 --- a/VEX/priv/guest-amd64/toIR.c +++ b/VEX/priv/guest-amd64/toIR.c @@ -771,7 +771,8 @@ static IRType szToITy ( Int n ) case 2: return Ity_I16; case 4: return Ity_I32; case 8: return Ity_I64; - default: vpanic("szToITy(amd64)"); + default: vex_printf("\nszToITy(%d)\n", n); + vpanic("szToITy(amd64)"); } } @@ -6696,112 +6697,123 @@ ULong dis_FPU ( /*OUT*/Bool* decode_ok, //.. //.. return delta; //.. } -//.. -//.. -//.. -//.. /* Handle BSF/BSR. Only v-size seems necessary. */ -//.. static -//.. UInt dis_bs_E_G ( UChar sorb, Int sz, ULong delta, Bool fwds ) -//.. { -//.. Bool isReg; -//.. UChar modrm; -//.. HChar dis_buf[50]; -//.. -//.. IRType ty = szToITy(sz); -//.. IRTemp src = newTemp(ty); -//.. IRTemp dst = newTemp(ty); -//.. -//.. IRTemp src32 = newTemp(Ity_I32); -//.. IRTemp dst32 = newTemp(Ity_I32); -//.. IRTemp src8 = newTemp(Ity_I8); -//.. -//.. vassert(sz == 4 || sz == 2); -//.. -//.. modrm = getUChar(delta); -//.. -//.. isReg = epartIsReg(modrm); -//.. if (isReg) { -//.. delta++; -//.. assign( src, getIReg(sz, eregOfRM(modrm)) ); -//.. } else { -//.. Int len; -//.. IRTemp addr = disAMode( &len, sorb, delta, dis_buf ); -//.. delta += len; -//.. assign( src, loadLE(ty, mkexpr(addr)) ); -//.. } -//.. -//.. DIP("bs%c%c %s, %s\n", -//.. fwds ? 'f' : 'r', nameISize(sz), -//.. ( isReg ? nameIReg(sz, eregOfRM(modrm)) : dis_buf ), -//.. nameIReg(sz, gregOfRM(modrm))); -//.. -//.. /* Generate an 8-bit expression which is zero iff the -//.. original is zero, and nonzero otherwise */ -//.. assign( src8, -//.. unop(Iop_1Uto8, binop(mkSizedOp(ty,Iop_CmpNE8), -//.. mkexpr(src), mkU(ty,0))) ); -//.. -//.. /* Flags: Z is 1 iff source value is zero. All others -//.. are undefined -- we force them to zero. */ -//.. stmt( IRStmt_Put( OFFB_CC_OP, mkU32(X86G_CC_OP_COPY) )); -//.. stmt( IRStmt_Put( OFFB_CC_DEP2, mkU32(0) )); -//.. stmt( IRStmt_Put( -//.. OFFB_CC_DEP1, -//.. IRExpr_Mux0X( mkexpr(src8), -//.. /* src==0 */ -//.. mkU32(X86G_CC_MASK_Z), -//.. /* src!=0 */ -//.. mkU32(0) -//.. ) -//.. )); -//.. -//.. /* Result: iff source value is zero, we can't use -//.. Iop_Clz32/Iop_Ctz32 as they have no defined result in that case. -//.. But anyway, Intel x86 semantics say the result is undefined in -//.. such situations. Hence handle the zero case specially. */ -//.. -//.. /* Bleh. What we compute: -//.. -//.. bsf32: if src == 0 then 0 else Ctz32(src) -//.. bsr32: if src == 0 then 0 else 31 - Clz32(src) -//.. -//.. bsf16: if src == 0 then 0 else Ctz32(16Uto32(src)) -//.. bsr16: if src == 0 then 0 else 31 - Clz32(16Uto32(src)) -//.. -//.. First, widen src to 32 bits if it is not already. -//.. -//.. Postscript 15 Oct 04: it seems that at least VIA Nehemiah leaves the -//.. dst register unchanged when src == 0. Hence change accordingly. -//.. */ -//.. if (sz == 2) -//.. assign( src32, unop(Iop_16Uto32, mkexpr(src)) ); -//.. else -//.. assign( src32, mkexpr(src) ); -//.. -//.. /* The main computation, guarding against zero. */ -//.. assign( dst32, -//.. IRExpr_Mux0X( -//.. mkexpr(src8), -//.. /* src == 0 -- leave dst unchanged */ -//.. widenUto32( getIReg( sz, gregOfRM(modrm) ) ), -//.. /* src != 0 */ -//.. fwds ? unop(Iop_Ctz32, mkexpr(src32)) -//.. : binop(Iop_Sub32, -//.. mkU32(31), -//.. unop(Iop_Clz32, mkexpr(src32))) -//.. ) -//.. ); -//.. -//.. if (sz == 2) -//.. assign( dst, unop(Iop_32to16, mkexpr(dst32)) ); -//.. else -//.. assign( dst, mkexpr(dst32) ); -//.. -//.. /* dump result back */ -//.. putIReg( sz, gregOfRM(modrm), mkexpr(dst) ); -//.. -//.. return delta; -//.. } + + + +/* Handle BSF/BSR. Only v-size seems necessary. */ +static +ULong dis_bs_E_G ( Prefix pfx, Int sz, ULong delta, Bool fwds ) +{ + Bool isReg; + UChar modrm; + HChar dis_buf[50]; + + IRType ty = szToITy(sz); + IRTemp src = newTemp(ty); + IRTemp dst = newTemp(ty); + IRTemp src64 = newTemp(Ity_I64); + IRTemp dst64 = newTemp(Ity_I64); + IRTemp src8 = newTemp(Ity_I8); + + vassert(sz == 8 || sz == 4 || sz == 2); + + modrm = getUChar(delta); + isReg = epartIsReg(modrm); + if (isReg) { + delta++; + assign( src, getIRegE(sz, pfx, modrm) ); + } else { + Int len; + IRTemp addr = disAMode( &len, pfx, delta, dis_buf, 0 ); + delta += len; + assign( src, loadLE(ty, mkexpr(addr)) ); + } + + DIP("bs%c%c %s, %s\n", + fwds ? 'f' : 'r', nameISize(sz), + ( isReg ? nameIRegE(sz, pfx, modrm) : dis_buf ), + nameIRegG(sz, pfx, modrm)); + + /* First, widen src to 64 bits if it is not already. */ + assign( src64, widenUto64(mkexpr(src)) ); + + /* Generate an 8-bit expression which is zero iff the + original is zero, and nonzero otherwise */ + assign( src8, + unop(Iop_1Uto8, + binop(Iop_CmpNE64, + mkexpr(src64), mkU64(0))) ); + + /* Flags: Z is 1 iff source value is zero. All others + are undefined -- we force them to zero. */ + stmt( IRStmt_Put( OFFB_CC_OP, mkU64(AMD64G_CC_OP_COPY) )); + stmt( IRStmt_Put( OFFB_CC_DEP2, mkU64(0) )); + stmt( IRStmt_Put( + OFFB_CC_DEP1, + IRExpr_Mux0X( mkexpr(src8), + /* src==0 */ + mkU64(AMD64G_CC_MASK_Z), + /* src!=0 */ + mkU64(0) + ) + )); + /* Set NDEP even though it isn't used. This makes redundant-PUT + elimination of previous stores to this field work better. */ + stmt( IRStmt_Put( OFFB_CC_NDEP, mkU64(0) )); + + /* Result: iff source value is zero, we can't use + Iop_Clz64/Iop_Ctz64 as they have no defined result in that case. + But anyway, amd64 semantics say the result is undefined in + such situations. Hence handle the zero case specially. */ + + /* Bleh. What we compute: + + bsf64: if src == 0 then {dst is unchanged} + else Ctz64(src) + + bsr64: if src == 0 then {dst is unchanged} + else 63 - Clz64(src) + + bsf32: if src == 0 then {dst is unchanged} + else Ctz64(32Uto64(src)) + + bsr32: if src == 0 then {dst is unchanged} + else 63 - Clz64(32Uto64(src)) + + bsf16: if src == 0 then {dst is unchanged} + else Ctz64(32Uto64(16Uto32(src))) + + bsr16: if src == 0 then {dst is unchanged} + else 63 - Clz64(32Uto64(16Uto32(src))) + */ + + /* The main computation, guarding against zero. */ + assign( dst64, + IRExpr_Mux0X( + mkexpr(src8), + /* src == 0 -- leave dst unchanged */ + widenUto64( getIRegG( sz, pfx, modrm ) ), + /* src != 0 */ + fwds ? unop(Iop_Ctz64, mkexpr(src64)) + : binop(Iop_Sub64, + mkU64(63), + unop(Iop_Clz64, mkexpr(src64))) + ) + ); + + if (sz == 2) + assign( dst, unop(Iop_32to16, unop(Iop_64to32, mkexpr(dst64))) ); + else + if (sz == 4) + assign( dst, unop(Iop_64to32, mkexpr(dst64)) ); + else + assign( dst, mkexpr(dst64) ); + + /* dump result back */ + putIRegG( sz, pfx, modrm, mkexpr(dst) ); + + return delta; +} /* swap rAX with the reg specified by reg and REX.B */ @@ -8975,19 +8987,20 @@ DisResult disInstr ( /*IN*/ Bool resteerOK, //.. "rsqrtss", Iop_RSqrt32F0x4 ); //.. goto decode_success; //.. } -//.. -//.. /* 0F AE /7 = SFENCE -- flush pending operations to memory */ -//.. if (insn[0] == 0x0F && insn[1] == 0xAE -//.. && epartIsReg(insn[2]) && gregOfRM(insn[2]) == 7) { -//.. vassert(sz == 4); -//.. delta += 3; -//.. /* Insert a memory fence. It's sometimes important that these -//.. are carried through to the generated code. */ -//.. stmt( IRStmt_MFence() ); -//.. DIP("sfence\n"); -//.. goto decode_success; -//.. } -//.. + + /* 0F AE /7 = SFENCE -- flush pending operations to memory */ + if (haveNo66noF2noF3(pfx) + && insn[0] == 0x0F && insn[1] == 0xAE + && epartIsReg(insn[2]) && gregLO3ofRM(insn[2]) == 7 + && sz == 4) { + delta += 3; + /* Insert a memory fence. It's sometimes important that these + are carried through to the generated code. */ + stmt( IRStmt_MFence() ); + DIP("sfence\n"); + goto decode_success; + } + //.. /* 0F C6 /r ib = SHUFPS -- shuffle packed F32s */ //.. if (sz == 4 && insn[0] == 0x0F && insn[1] == 0xC6) { //.. Int select; @@ -10123,23 +10136,24 @@ DisResult disInstr ( /*IN*/ Bool resteerOK, //.. goto decode_success; //.. } //.. /* else fall through */ -//.. } -//.. -//.. /* 0F C3 = MOVNTI -- for us, just a plain ireg store. */ -//.. if (insn[0] == 0x0F && insn[1] == 0xC3) { -//.. vassert(sz == 4); -//.. modrm = getUChar(delta+2); -//.. if (!epartIsReg(modrm)) { -//.. addr = disAMode ( &alen, sorb, delta+2, dis_buf ); -//.. storeLE( mkexpr(addr), getIReg(4, gregOfRM(modrm)) ); -//.. DIP("movnti %s,%s\n", dis_buf, -//.. nameIReg(4, gregOfRM(modrm))); -//.. delta += 2+alen; -//.. goto decode_success; -//.. } -//.. /* else fall through */ //.. } + /* 0F C3 = MOVNTI -- for us, just a plain ireg store. */ + if (haveNo66noF2noF3(pfx) && + insn[0] == 0x0F && insn[1] == 0xC3) { + vassert(sz == 4 || sz == 8); + modrm = getUChar(delta+2); + if (!epartIsReg(modrm)) { + addr = disAMode ( &alen, pfx, delta+2, dis_buf, 0 ); + storeLE( mkexpr(addr), getIRegG(sz, pfx, modrm) ); + DIP("movnti %s,%s\n", dis_buf, + nameIRegG(sz, pfx, modrm)); + delta += 2+alen; + goto decode_success; + } + /* else fall through */ + } + /* 66 0F D6 = MOVQ -- move 64 bits from G (lo half xmm) to E (mem or lo half xmm). */ if (have66noF2noF3(pfx) && insn[0] == 0x0F && insn[1] == 0xD6) { @@ -12843,11 +12857,12 @@ DisResult disInstr ( /*IN*/ Bool resteerOK, break; } -//.. /* =-=-=-=-=-=-=-=-=- BSF/BSR -=-=-=-=-=-=-=-=-=-= */ -//.. -//.. case 0xBC: /* BSF Gv,Ev */ -//.. delta = dis_bs_E_G ( sorb, sz, delta, True ); -//.. break; + /* =-=-=-=-=-=-=-=-=- BSF/BSR -=-=-=-=-=-=-=-=-=-= */ + + case 0xBC: /* BSF Gv,Ev */ + if (haveF2orF3(pfx)) goto decode_failure; + delta = dis_bs_E_G ( pfx, sz, delta, True ); + break; //.. case 0xBD: /* BSR Gv,Ev */ //.. delta = dis_bs_E_G ( sorb, sz, delta, False ); //.. break; diff --git a/VEX/priv/host-amd64/hdefs.c b/VEX/priv/host-amd64/hdefs.c index 821def08f7..c17961d337 100644 --- a/VEX/priv/host-amd64/hdefs.c +++ b/VEX/priv/host-amd64/hdefs.c @@ -779,14 +779,14 @@ AMD64Instr* AMD64Instr_Set64 ( AMD64CondCode cond, HReg dst ) { i->Ain.Set64.dst = dst; return i; } -//.. AMD64Instr* AMD64Instr_Bsfr32 ( Bool isFwds, HReg src, HReg dst ) { -//.. AMD64Instr* i = LibVEX_Alloc(sizeof(AMD64Instr)); -//.. i->tag = Xin_Bsfr32; -//.. i->Xin.Bsfr32.isFwds = isFwds; -//.. i->Xin.Bsfr32.src = src; -//.. i->Xin.Bsfr32.dst = dst; -//.. return i; -//.. } +AMD64Instr* AMD64Instr_Bsfr64 ( Bool isFwds, HReg src, HReg dst ) { + AMD64Instr* i = LibVEX_Alloc(sizeof(AMD64Instr)); + i->tag = Ain_Bsfr64; + i->Ain.Bsfr64.isFwds = isFwds; + i->Ain.Bsfr64.src = src; + i->Ain.Bsfr64.dst = dst; + return i; +} AMD64Instr* AMD64Instr_MFence ( void ) { AMD64Instr* i = LibVEX_Alloc(sizeof(AMD64Instr)); @@ -1120,12 +1120,12 @@ void ppAMD64Instr ( AMD64Instr* i ) vex_printf("setq%s ", showAMD64CondCode(i->Ain.Set64.cond)); ppHRegAMD64(i->Ain.Set64.dst); return; -//.. case Xin_Bsfr32: -//.. vex_printf("bs%cl ", i->Xin.Bsfr32.isFwds ? 'f' : 'r'); -//.. ppHRegAMD64(i->Xin.Bsfr32.src); -//.. vex_printf(","); -//.. ppHRegAMD64(i->Xin.Bsfr32.dst); -//.. return; + case Ain_Bsfr64: + vex_printf("bs%cq ", i->Ain.Bsfr64.isFwds ? 'f' : 'r'); + ppHRegAMD64(i->Ain.Bsfr64.src); + vex_printf(","); + ppHRegAMD64(i->Ain.Bsfr64.dst); + return; case Ain_MFence: vex_printf("mfence" ); return; @@ -1433,10 +1433,10 @@ void getRegUsage_AMD64Instr ( HRegUsage* u, AMD64Instr* i ) case Ain_Set64: addHRegUse(u, HRmWrite, i->Ain.Set64.dst); return; -//.. case Xin_Bsfr32: -//.. addHRegUse(u, HRmRead, i->Xin.Bsfr32.src); -//.. addHRegUse(u, HRmWrite, i->Xin.Bsfr32.dst); -//.. return; + case Ain_Bsfr64: + addHRegUse(u, HRmRead, i->Ain.Bsfr64.src); + addHRegUse(u, HRmWrite, i->Ain.Bsfr64.dst); + return; case Ain_MFence: return; //.. case Xin_FpUnary: @@ -1631,10 +1631,10 @@ void mapRegs_AMD64Instr ( HRegRemap* m, AMD64Instr* i ) case Ain_Set64: mapReg(m, &i->Ain.Set64.dst); return; -//.. case Xin_Bsfr32: -//.. mapReg(m, &i->Xin.Bsfr32.src); -//.. mapReg(m, &i->Xin.Bsfr32.dst); -//.. return; + case Ain_Bsfr64: + mapReg(m, &i->Ain.Bsfr64.src); + mapReg(m, &i->Ain.Bsfr64.dst); + return; case Ain_MFence: return; //.. case Xin_FpUnary: @@ -2695,15 +2695,16 @@ Int emit_AMD64Instr ( UChar* buf, Int nbuf, AMD64Instr* i ) *p++ = toUChar(0xC0 + (reg & 7)); goto done; -//.. case Xin_Bsfr32: -//.. *p++ = 0x0F; -//.. if (i->Xin.Bsfr32.isFwds) { -//.. *p++ = 0xBC; -//.. } else { -//.. *p++ = 0xBD; -//.. } -//.. p = doAMode_R(p, i->Xin.Bsfr32.dst, i->Xin.Bsfr32.src); -//.. goto done; + case Ain_Bsfr64: + *p++ = rexAMode_R(i->Ain.Bsfr64.dst, i->Ain.Bsfr64.src); + *p++ = 0x0F; + if (i->Ain.Bsfr64.isFwds) { + *p++ = 0xBC; + } else { + *p++ = 0xBD; + } + p = doAMode_R(p, i->Ain.Bsfr64.dst, i->Ain.Bsfr64.src); + goto done; case Ain_MFence: /* mfence */ diff --git a/VEX/priv/host-amd64/hdefs.h b/VEX/priv/host-amd64/hdefs.h index ed9299f820..b721f03fbf 100644 --- a/VEX/priv/host-amd64/hdefs.h +++ b/VEX/priv/host-amd64/hdefs.h @@ -370,7 +370,7 @@ typedef Ain_LoadEX, /* mov{s,z}{b,w,l}q from mem to reg */ Ain_Store, /* store 32/16/8 bit value in memory */ Ain_Set64, /* convert condition code to 32-bit value */ -//.. Xin_Bsfr32, /* 32-bit bsf/bsr */ + Ain_Bsfr64, /* 64-bit bsf/bsr */ Ain_MFence, /* mem fence */ //.. //.. Xin_FpUnary, /* FP fake unary op */ @@ -503,12 +503,12 @@ typedef AMD64CondCode cond; HReg dst; } Set64; -//.. /* 32-bit bsf or bsr. */ -//.. struct { -//.. Bool isFwds; -//.. HReg src; -//.. HReg dst; -//.. } Bsfr32; + /* 64-bit bsf or bsr. */ + struct { + Bool isFwds; + HReg src; + HReg dst; + } Bsfr64; /* Mem fence. In short, an insn which flushes all preceding loads and stores as much as possible before continuing. On AMD64 we emit a real "mfence". */ @@ -670,7 +670,7 @@ extern AMD64Instr* AMD64Instr_LoadEX ( UChar szSmall, Bool syned, AMD64AMode* src, HReg dst ); extern AMD64Instr* AMD64Instr_Store ( UChar sz, HReg src, AMD64AMode* dst ); extern AMD64Instr* AMD64Instr_Set64 ( AMD64CondCode cond, HReg dst ); -//.. extern AMD64Instr* AMD64Instr_Bsfr32 ( Bool isFwds, HReg src, HReg dst ); +extern AMD64Instr* AMD64Instr_Bsfr64 ( Bool isFwds, HReg src, HReg dst ); extern AMD64Instr* AMD64Instr_MFence ( void ); //.. //.. extern AMD64Instr* AMD64Instr_FpUnary ( AMD64FpOp op, HReg src, HReg dst ); diff --git a/VEX/priv/host-amd64/isel.c b/VEX/priv/host-amd64/isel.c index 1063049f06..7e8602fb21 100644 --- a/VEX/priv/host-amd64/isel.c +++ b/VEX/priv/host-amd64/isel.c @@ -1276,12 +1276,12 @@ static HReg iselIntExpr_R_wrk ( ISelEnv* env, IRExpr* e ) return dst; } //.. case Iop_1Uto32: -//.. case Iop_1Uto8: { -//.. HReg dst = newVRegI(env); -//.. X86CondCode cond = iselCondCode(env, e->Iex.Unop.arg); -//.. addInstr(env, X86Instr_Set32(cond,dst)); -//.. return dst; -//.. } + case Iop_1Uto8: { + HReg dst = newVRegI(env); + AMD64CondCode cond = iselCondCode(env, e->Iex.Unop.arg); + addInstr(env, AMD64Instr_Set64(cond,dst)); + return dst; + } //.. case Iop_1Sto8: //.. case Iop_1Sto16: //.. case Iop_1Sto32: { @@ -1293,13 +1293,13 @@ static HReg iselIntExpr_R_wrk ( ISelEnv* env, IRExpr* e ) //.. addInstr(env, X86Instr_Sh32(Xsh_SAR, 31, X86RM_Reg(dst))); //.. return dst; //.. } -//.. case Iop_Ctz32: { -//.. /* Count trailing zeroes, implemented by x86 'bsfl' */ -//.. HReg dst = newVRegI(env); -//.. HReg src = iselIntExpr_R(env, e->Iex.Unop.arg); -//.. addInstr(env, X86Instr_Bsfr32(True,src,dst)); -//.. return dst; -//.. } + case Iop_Ctz64: { + /* Count trailing zeroes, implemented by amd64 'bsfq' */ + HReg dst = newVRegI(env); + HReg src = iselIntExpr_R(env, e->Iex.Unop.arg); + addInstr(env, AMD64Instr_Bsfr64(True,src,dst)); + return dst; + } //.. case Iop_Clz32: { //.. /* Count leading zeroes. Do 'bsrl' to establish the index //.. of the highest set bit, and subtract that value from diff --git a/VEX/priv/ir/irdefs.c b/VEX/priv/ir/irdefs.c index acb58ff91d..2bc6f69e76 100644 --- a/VEX/priv/ir/irdefs.c +++ b/VEX/priv/ir/irdefs.c @@ -161,7 +161,9 @@ void ppIROp ( IROp op ) case Iop_MullU32: vex_printf("MullU32"); return; case Iop_MullU64: vex_printf("MullU64"); return; + case Iop_Clz64: vex_printf("Clz64"); return; case Iop_Clz32: vex_printf("Clz32"); return; + case Iop_Ctz64: vex_printf("Ctz64"); return; case Iop_Ctz32: vex_printf("Ctz32"); return; case Iop_CmpLT32S: vex_printf("CmpLT32S"); return; @@ -1283,6 +1285,9 @@ void typeOfPrimop ( IROp op, IRType* t_dst, IRType* t_arg1, IRType* t_arg2 ) case Iop_Clz32: case Iop_Ctz32: UNARY(Ity_I32,Ity_I32); + case Iop_Clz64: case Iop_Ctz64: + UNARY(Ity_I64,Ity_I64); + case Iop_DivU32: case Iop_DivS32: BINARY(Ity_I32, Ity_I32,Ity_I32); diff --git a/VEX/pub/libvex_ir.h b/VEX/pub/libvex_ir.h index d183f18691..df7c1d0c18 100644 --- a/VEX/pub/libvex_ir.h +++ b/VEX/pub/libvex_ir.h @@ -223,10 +223,10 @@ typedef Iop_MullU8, Iop_MullU16, Iop_MullU32, Iop_MullU64, /* Wierdo integer stuff */ - Iop_Clz32, /* count leading zeroes */ - Iop_Ctz32, /* count trailing zeros */ - /* Ctz32/Clz32 are UNDEFINED when given arguments of zero. - You must ensure they are never given a zero argument. + Iop_Clz64, Iop_Clz32, /* count leading zeroes */ + Iop_Ctz64, Iop_Ctz32, /* count trailing zeros */ + /* Ctz64/Ctz32/Clz64/Clz32 are UNDEFINED when given arguments of + zero. You must ensure they are never given a zero argument. */ /* Ordering not important after here. */