- changed OP_PARAM handling so that all registers remain allocated until the call instruction and reordered instruction emission so that the param instructions all directly precede the call instruction.

This commit is contained in:
Christoph Oelckers 2018-11-18 01:06:04 +01:00
commit 983c0c56b1
4 changed files with 273 additions and 209 deletions

View file

@ -485,7 +485,7 @@ PPrototype *FxExpression::ReturnProto()
//
//==========================================================================
static int EncodeRegType(ExpEmit reg)
int EncodeRegType(ExpEmit reg)
{
int regtype = reg.RegType;
if (reg.Fixed && reg.Target)
@ -508,36 +508,6 @@ static int EncodeRegType(ExpEmit reg)
return regtype;
}
//==========================================================================
//
//
//
//==========================================================================
static int EmitParameter(VMFunctionBuilder *build, FxExpression *operand, const FScriptPosition &pos, TArray<ExpEmit> *tempstrings = nullptr)
{
ExpEmit where = operand->Emit(build);
if (where.RegType == REGT_NIL)
{
pos.Message(MSG_ERROR, "Attempted to pass a non-value");
build->Emit(OP_PARAM, where.RegType, where.RegNum);
return 1;
}
else
{
build->Emit(OP_PARAM, EncodeRegType(where), where.RegNum);
if (tempstrings != nullptr && where.RegType == REGT_STRING && !where.Fixed && !where.Konst)
{
tempstrings->Push(where); // keep temp strings until after the function call.
}
else
{
where.Free(build);
}
return where.RegCount;
}
}
//==========================================================================
//
@ -5539,10 +5509,13 @@ ExpEmit FxRandom::Emit(VMFunctionBuilder *build)
if (build->FramePointer.Fixed) EmitTail = false; // do not tail call if the stack is in use
int opcode = (EmitTail ? OP_TAIL_K : OP_CALL_K);
build->Emit(OP_PARAM, REGT_POINTER | REGT_KONST, build->GetConstantAddress(rng));
EmitParameter(build, min, ScriptPosition);
EmitParameter(build, max, ScriptPosition);
build->Emit(opcode, build->GetConstantAddress(callfunc), 3, 1);
EmitterArray emitters;
emitters.AddParameterPointerConst(rng);
emitters.AddParameter(build, min);
emitters.AddParameter(build, max);
int count = emitters.EmitParameters(build);
build->Emit(opcode, build->GetConstantAddress(callfunc), count, 1);
if (EmitTail)
{
@ -5653,10 +5626,12 @@ ExpEmit FxRandomPick::Emit(VMFunctionBuilder *build)
assert(((PSymbolVMFunction *)sym)->Function != nullptr);
callfunc = ((PSymbolVMFunction *)sym)->Function;
build->Emit(OP_PARAM, REGT_POINTER | REGT_KONST, build->GetConstantAddress(rng));
build->EmitParamInt(0);
build->EmitParamInt(choices.Size() - 1);
build->Emit(OP_CALL_K, build->GetConstantAddress(callfunc), 3, 1);
EmitterArray emitters;
emitters.AddParameterPointerConst(rng);
emitters.AddParameterIntConst(0);
emitters.AddParameterIntConst(choices.Size() - 1);
int count = emitters.EmitParameters(build);
build->Emit(OP_CALL_K, build->GetConstantAddress(callfunc), count, 1);
ExpEmit resultreg(build, REGT_INT);
build->Emit(OP_RESULT, 0, REGT_INT, resultreg.RegNum);
@ -5775,10 +5750,12 @@ ExpEmit FxFRandom::Emit(VMFunctionBuilder *build)
if (build->FramePointer.Fixed) EmitTail = false; // do not tail call if the stack is in use
int opcode = (EmitTail ? OP_TAIL_K : OP_CALL_K);
build->Emit(OP_PARAM, REGT_POINTER | REGT_KONST, build->GetConstantAddress(rng));
EmitParameter(build, min, ScriptPosition);
EmitParameter(build, max, ScriptPosition);
build->Emit(opcode, build->GetConstantAddress(callfunc), 3, 1);
EmitterArray emitters;
emitters.AddParameterPointerConst(rng);
emitters.AddParameter(build, min);
emitters.AddParameter(build, max);
int count = emitters.EmitParameters(build);
build->Emit(opcode, build->GetConstantAddress(callfunc), count, 1);
if (EmitTail)
{
@ -5877,9 +5854,12 @@ ExpEmit FxRandom2::Emit(VMFunctionBuilder *build)
if (build->FramePointer.Fixed) EmitTail = false; // do not tail call if the stack is in use
int opcode = (EmitTail ? OP_TAIL_K : OP_CALL_K);
build->Emit(OP_PARAM, REGT_POINTER | REGT_KONST, build->GetConstantAddress(rng));
EmitParameter(build, mask, ScriptPosition);
build->Emit(opcode, build->GetConstantAddress(callfunc), 2, 1);
EmitterArray emitters;
emitters.AddParameterPointerConst(rng);
emitters.AddParameter(build, mask);
int count = emitters.EmitParameters(build);
build->Emit(opcode, build->GetConstantAddress(callfunc), count, 1);
if (EmitTail)
{
@ -5960,9 +5940,12 @@ ExpEmit FxRandomSeed::Emit(VMFunctionBuilder *build)
if (build->FramePointer.Fixed) EmitTail = false; // do not tail call if the stack is in use
int opcode = (EmitTail ? OP_TAIL_K : OP_CALL_K);
build->Emit(OP_PARAM, REGT_POINTER | REGT_KONST, build->GetConstantAddress(rng));
EmitParameter(build, seed, ScriptPosition);
build->Emit(opcode, build->GetConstantAddress(callfunc), 2, 0);
EmitterArray emitters;
emitters.AddParameterPointerConst(rng);
emitters.AddParameter(build, seed);
int count = emitters.EmitParameters(build);
build->Emit(opcode, build->GetConstantAddress(callfunc), count, 0);
ExpEmit call;
if (EmitTail) call.Final = true;
@ -8642,10 +8625,13 @@ ExpEmit FxActionSpecialCall::Emit(VMFunctionBuilder *build)
{
unsigned i = 0;
build->Emit(OP_PARAMI, abs(Special)); // pass special number
EmitterArray emitters;
emitters.AddParameterIntConst(abs(Special)); // pass special number
ExpEmit selfemit(Self->Emit(build));
build->Emit(OP_PARAM, selfemit.Konst ? REGT_POINTER | REGT_KONST : REGT_POINTER, selfemit.RegNum); // pass special number
emitters.AddParameterPointer(selfemit.RegNum, selfemit.Konst);
for (; i < ArgList.Size(); ++i)
{
FxExpression *argex = ArgList[i];
@ -8653,20 +8639,18 @@ ExpEmit FxActionSpecialCall::Emit(VMFunctionBuilder *build)
{
assert(argex->ValueType == TypeName);
assert(argex->isConstant());
build->EmitParamInt(-static_cast<FxConstant *>(argex)->GetValue().GetName());
emitters.AddParameterIntConst(-static_cast<FxConstant *>(argex)->GetValue().GetName());
}
else
{
assert(argex->ValueType->GetRegType() == REGT_INT);
if (argex->isConstant())
{
build->EmitParamInt(static_cast<FxConstant *>(argex)->GetValue().GetInt());
emitters.AddParameterIntConst(static_cast<FxConstant *>(argex)->GetValue().GetInt());
}
else
{
ExpEmit arg(argex->Emit(build));
build->Emit(OP_PARAM, arg.RegType, arg.RegNum);
arg.Free(build);
emitters.AddParameter(build, argex);
}
}
}
@ -8681,16 +8665,18 @@ ExpEmit FxActionSpecialCall::Emit(VMFunctionBuilder *build)
ArgList.ShrinkToFit();
if (build->FramePointer.Fixed) EmitTail = false; // do not tail call if the stack is in use
int count = emitters.EmitParameters(build);
selfemit.Free(build);
if (EmitTail)
{
build->Emit(OP_TAIL_K, build->GetConstantAddress(callfunc), 2 + i, 0);
build->Emit(OP_TAIL_K, build->GetConstantAddress(callfunc), count, 0);
ExpEmit call;
call.Final = true;
return call;
}
ExpEmit dest(build, REGT_INT);
build->Emit(OP_CALL_K, build->GetConstantAddress(callfunc), 2 + i, 1);
build->Emit(OP_CALL_K, build->GetConstantAddress(callfunc), count, 1);
build->Emit(OP_RESULT, 0, REGT_INT, dest.RegNum);
return dest;
}
@ -9079,8 +9065,6 @@ FxExpression *FxVMFunctionCall::Resolve(FCompileContext& ctx)
ExpEmit FxVMFunctionCall::Emit(VMFunctionBuilder *build)
{
TArray<ExpEmit> tempstrings;
assert(build->Registers[REGT_POINTER].GetMostUsed() >= build->NumImplicits);
int count = 0;
@ -9093,7 +9077,6 @@ ExpEmit FxVMFunctionCall::Emit(VMFunctionBuilder *build)
{
ArgList.DeleteAndClear();
ArgList.ShrinkToFit();
for (auto & exp : tempstrings) exp.Free(build);
return reg;
}
}
@ -9102,6 +9085,7 @@ ExpEmit FxVMFunctionCall::Emit(VMFunctionBuilder *build)
bool staticcall = ((vmfunc->VarFlags & VARF_Final) || vmfunc->VirtualIndex == ~0u || NoVirtual);
count = 0;
EmitterArray emitters;
// Emit code to pass implied parameters
ExpEmit selfemit;
if (Function->Variants[0].Flags & VARF_Method)
@ -9128,64 +9112,51 @@ ExpEmit FxVMFunctionCall::Emit(VMFunctionBuilder *build)
}
}
if ((selfemit.Fixed && selfemit.Target) || selfemit.RegType == REGT_STRING)
{
// Address of a local variable.
build->Emit(OP_PARAM, selfemit.RegType | REGT_ADDROF, selfemit.RegNum);
}
else
{
build->Emit(OP_PARAM, selfemit.RegType, selfemit.RegNum);
}
count += 1;
emitters.AddParameter(selfemit, (selfemit.Fixed && selfemit.Target) || selfemit.RegType == REGT_STRING);
if (Function->Variants[0].Flags & VARF_Action)
{
static_assert(NAP == 3, "This code needs to be updated if NAP changes");
if (build->NumImplicits == NAP && selfemit.RegNum == 0) // only pass this function's stateowner and stateinfo if the subfunction is run in self's context.
{
build->Emit(OP_PARAM, REGT_POINTER, 1);
build->Emit(OP_PARAM, REGT_POINTER, 2);
emitters.AddParameterPointer(1, false);
emitters.AddParameterPointer(2, false);
}
else
{
// pass self as stateowner, otherwise all attempts of the subfunction to retrieve a state from a name would fail.
build->Emit(OP_PARAM, selfemit.RegType, selfemit.RegNum);
build->Emit(OP_PARAM, REGT_POINTER | REGT_KONST, build->GetConstantAddress(nullptr));
emitters.AddParameter(selfemit, (selfemit.Fixed && selfemit.Target) || selfemit.RegType == REGT_STRING);
emitters.AddParameterPointerConst(nullptr);
}
count += 2;
}
if (staticcall) selfemit.Free(build);
}
else staticcall = true;
// Emit code to pass explicit parameters
for (unsigned i = 0; i < ArgList.Size(); ++i)
{
count += EmitParameter(build, ArgList[i], ScriptPosition, &tempstrings);
emitters.AddParameter(build, ArgList[i]);
}
// Complete the parameter list from the defaults.
auto &defaults = Function->Variants[0].Implementation->DefaultArgs;
for (unsigned i = count; i < defaults.Size(); i++)
for (unsigned i = emitters.Count(); i < defaults.Size(); i++)
{
FxConstant *constant;
switch (defaults[i].Type)
{
default:
case REGT_INT:
constant = new FxConstant(defaults[i].i, ScriptPosition);
emitters.AddParameterIntConst(defaults[i].i);
break;
case REGT_FLOAT:
constant = new FxConstant(defaults[i].f, ScriptPosition);
emitters.AddParameterFloatConst(defaults[i].f);
break;
case REGT_POINTER:
constant = new FxConstant(defaults[i].a, ScriptPosition);
emitters.AddParameterPointerConst(defaults[i].a);
break;
case REGT_STRING:
constant = new FxConstant(defaults[i].s(), ScriptPosition);
emitters.AddParameterStringConst(defaults[i].s());
break;
}
count += EmitParameter(build, constant, ScriptPosition, &tempstrings);
delete constant;
}
count = emitters.EmitParameters(build);
ArgList.DeleteAndClear();
ArgList.ShrinkToFit();
@ -9200,7 +9171,6 @@ ExpEmit FxVMFunctionCall::Emit(VMFunctionBuilder *build)
build->Emit(OP_TAIL_K, funcaddr, count, 0);
ExpEmit call;
call.Final = true;
for (auto & exp : tempstrings) exp.Free(build);
return call;
}
else if (vmfunc->Proto->ReturnTypes.Size() > 0)
@ -9211,13 +9181,11 @@ ExpEmit FxVMFunctionCall::Emit(VMFunctionBuilder *build)
else
{ // Call, expecting no results
build->Emit(OP_CALL_K, funcaddr, count, 0);
for (auto & exp : tempstrings) exp.Free(build);
return ExpEmit();
}
}
else
{
selfemit.Free(build);
ExpEmit funcreg(build, REGT_POINTER);
build->Emit(OP_VTBL, funcreg.RegNum, selfemit.RegNum, vmfunc->VirtualIndex);
@ -9226,7 +9194,6 @@ ExpEmit FxVMFunctionCall::Emit(VMFunctionBuilder *build)
build->Emit(OP_TAIL, funcreg.RegNum, count, 0);
ExpEmit call;
call.Final = true;
for (auto & exp : tempstrings) exp.Free(build);
return call;
}
else if (vmfunc->Proto->ReturnTypes.Size() > 0)
@ -9237,7 +9204,6 @@ ExpEmit FxVMFunctionCall::Emit(VMFunctionBuilder *build)
else
{ // Call, expecting no results
build->Emit(OP_CALL, funcreg.RegNum, count, 0);
for (auto & exp : tempstrings) exp.Free(build);
return ExpEmit();
}
}
@ -9247,7 +9213,6 @@ handlereturns:
// Regular call, will not write to ReturnRegs
ExpEmit reg(build, vmfunc->Proto->ReturnTypes[0]->GetRegType(), vmfunc->Proto->ReturnTypes[0]->GetRegCount());
build->Emit(OP_RESULT, 0, EncodeRegType(reg), reg.RegNum);
for (auto & exp : tempstrings) exp.Free(build);
return reg;
}
else
@ -9261,7 +9226,6 @@ handlereturns:
ReturnRegs.Push(reg);
}
}
for (auto & exp : tempstrings) exp.Free(build);
return ExpEmit();
}
@ -10773,9 +10737,10 @@ ExpEmit FxReturnStatement::Emit(VMFunctionBuilder *build)
assert(pstr->mDestructor != nullptr);
ExpEmit reg(build, REGT_POINTER);
build->Emit(OP_ADDA_RK, reg.RegNum, build->FramePointer.RegNum, build->GetConstantInt(build->ConstructedStructs[i]->StackOffset));
build->Emit(OP_PARAM, reg.RegType, reg.RegNum);
build->Emit(OP_CALL_K, build->GetConstantAddress(pstr->mDestructor), 1, 0);
reg.Free(build);
EmitterArray emitters;
emitters.AddParameter(reg, false);
int count = emitters.EmitParameters(build);
build->Emit(OP_CALL_K, build->GetConstantAddress(pstr->mDestructor), count, 0);
}
// If we return nothing, use a regular RET opcode.
@ -10970,8 +10935,9 @@ ExpEmit FxClassTypeCast::Emit(VMFunctionBuilder *build)
ExpEmit clsname = basex->Emit(build);
assert(!clsname.Konst);
ExpEmit dest(build, REGT_POINTER);
build->Emit(OP_PARAM, clsname.RegType, clsname.RegNum);
build->Emit(OP_PARAM, REGT_POINTER | REGT_KONST, build->GetConstantAddress(const_cast<PClass *>(desttype)));
EmitterArray emitters;
emitters.AddParameter(clsname, false);
emitters.AddParameterPointerConst(const_cast<PClass *>(desttype));
// Call the BuiltinNameToClass function to convert from 'name' to class.
VMFunction *callfunc;
@ -10981,9 +10947,9 @@ ExpEmit FxClassTypeCast::Emit(VMFunctionBuilder *build)
assert(((PSymbolVMFunction *)sym)->Function != nullptr);
callfunc = ((PSymbolVMFunction *)sym)->Function;
build->Emit(OP_CALL_K, build->GetConstantAddress(callfunc), 2, 1);
int count = emitters.EmitParameters(build);
build->Emit(OP_CALL_K, build->GetConstantAddress(callfunc), count, 1);
build->Emit(OP_RESULT, 0, REGT_POINTER, dest.RegNum);
clsname.Free(build);
return dest;
}
@ -11079,8 +11045,10 @@ ExpEmit FxClassPtrCast::Emit(VMFunctionBuilder *build)
{
ExpEmit clsname = basex->Emit(build);
build->Emit(OP_PARAM, clsname.RegType, clsname.RegNum);
build->Emit(OP_PARAM, REGT_POINTER | REGT_KONST, build->GetConstantAddress(desttype));
EmitterArray emitters;
emitters.AddParameter(clsname, false);
emitters.AddParameterPointerConst(desttype);
VMFunction *callfunc;
PSymbol *sym = FindBuiltinFunction(NAME_BuiltinClassCast, BuiltinClassCast);
@ -11088,9 +11056,9 @@ ExpEmit FxClassPtrCast::Emit(VMFunctionBuilder *build)
assert(sym->IsKindOf(RUNTIME_CLASS(PSymbolVMFunction)));
assert(((PSymbolVMFunction *)sym)->Function != nullptr);
callfunc = ((PSymbolVMFunction *)sym)->Function;
clsname.Free(build);
ExpEmit dest(build, REGT_POINTER);
build->Emit(OP_CALL_K, build->GetConstantAddress(callfunc), 2, 1);
int count = emitters.EmitParameters(build);
build->Emit(OP_CALL_K, build->GetConstantAddress(callfunc), count, 1);
build->Emit(OP_RESULT, 0, REGT_POINTER, dest.RegNum);
return dest;
}
@ -11465,9 +11433,10 @@ ExpEmit FxLocalVariableDeclaration::Emit(VMFunctionBuilder *build)
{
ExpEmit reg(build, REGT_POINTER);
build->Emit(OP_ADDA_RK, reg.RegNum, build->FramePointer.RegNum, build->GetConstantInt(StackOffset));
build->Emit(OP_PARAM, reg.RegType, reg.RegNum);
build->Emit(OP_CALL_K, build->GetConstantAddress(pstr->mConstructor), 1, 0);
reg.Free(build);
EmitterArray emitters;
emitters.AddParameter(reg, false);
int count = emitters.EmitParameters(build);
build->Emit(OP_CALL_K, build->GetConstantAddress(pstr->mConstructor), count, 0);
}
if (pstr->mDestructor != nullptr) build->ConstructedStructs.Push(this);
}
@ -11491,9 +11460,10 @@ void FxLocalVariableDeclaration::Release(VMFunctionBuilder *build)
{
ExpEmit reg(build, REGT_POINTER);
build->Emit(OP_ADDA_RK, reg.RegNum, build->FramePointer.RegNum, build->GetConstantInt(StackOffset));
build->Emit(OP_PARAM, reg.RegType, reg.RegNum);
build->Emit(OP_CALL_K, build->GetConstantAddress(pstr->mDestructor), 1, 0);
reg.Free(build);
EmitterArray emitters;
emitters.AddParameter(reg, false);
int count = emitters.EmitParameters(build);
build->Emit(OP_CALL_K, build->GetConstantAddress(pstr->mDestructor), count, 0);
}
build->ConstructedStructs.Delete(build->ConstructedStructs.Find(this));
}

View file

@ -654,28 +654,6 @@ size_t VMFunctionBuilder::Emit(int opcode, int opabc)
return Code.Push(op);
}
//==========================================================================
//
// VMFunctionBuilder :: EmitParamInt
//
// Passes a constant integer parameter, using either PARAMI and an immediate
// value or PARAM and a constant register, as appropriate.
//
//==========================================================================
size_t VMFunctionBuilder::EmitParamInt(int value)
{
// Immediates for PARAMI must fit in 24 bits.
if (((value << 8) >> 8) == value)
{
return Emit(OP_PARAMI, value);
}
else
{
return Emit(OP_PARAM, REGT_INT | REGT_KONST, GetConstantInt(value));
}
}
//==========================================================================
//
// VMFunctionBuilder :: EmitLoadInt
@ -951,3 +929,112 @@ void FFunctionBuildList::DumpJit()
fclose(dump);
}
void EmitterArray::AddParameter(VMFunctionBuilder *build, FxExpression *operand)
{
ExpEmit where = operand->Emit(build);
if (where.RegType == REGT_NIL)
{
operand->ScriptPosition.Message(MSG_ERROR, "Attempted to pass a non-value");
}
numparams += where.RegCount;
emitters.push_back([=](VMFunctionBuilder *build) -> int
{
auto op = where;
if (op.RegType == REGT_NIL)
{
build->Emit(OP_PARAM, op.RegType, op.RegNum);
return 1;
}
else
{
build->Emit(OP_PARAM, EncodeRegType(op), op.RegNum);
op.Free(build);
return op.RegCount;
}
});
}
void EmitterArray::AddParameter(ExpEmit &emit, bool reference)
{
numparams += emit.RegCount;
emitters.push_back([=](VMFunctionBuilder *build) ->int
{
build->Emit(OP_PARAM, emit.RegType + (reference * REGT_ADDROF), emit.RegNum);
auto op = emit;
op.Free(build);
return emit.RegCount;
});
}
void EmitterArray::AddParameterPointerConst(void *konst)
{
numparams++;
emitters.push_back([=](VMFunctionBuilder *build) ->int
{
build->Emit(OP_PARAM, REGT_POINTER | REGT_KONST, build->GetConstantAddress(konst));
return 1;
});
}
void EmitterArray::AddParameterPointer(int index, bool konst)
{
numparams++;
emitters.push_back([=](VMFunctionBuilder *build) ->int
{
build->Emit(OP_PARAM, konst ? REGT_POINTER | REGT_KONST : REGT_POINTER, index);
return 1;
});
}
void EmitterArray::AddParameterFloatConst(double konst)
{
numparams++;
emitters.push_back([=](VMFunctionBuilder *build) ->int
{
build->Emit(OP_PARAM, REGT_FLOAT | REGT_KONST, build->GetConstantFloat(konst));
return 1;
});
}
void EmitterArray::AddParameterIntConst(int konst)
{
numparams++;
emitters.push_back([=](VMFunctionBuilder *build) ->int
{
// Immediates for PARAMI must fit in 24 bits.
if (((konst << 8) >> 8) == konst)
{
build->Emit(OP_PARAMI, konst);
}
else
{
build->Emit(OP_PARAM, REGT_INT | REGT_KONST, build->GetConstantInt(konst));
}
return 1;
});
}
void EmitterArray::AddParameterStringConst(const FString &konst)
{
numparams++;
emitters.push_back([=](VMFunctionBuilder *build) ->int
{
build->Emit(OP_PARAM, REGT_STRING | REGT_KONST, build->GetConstantString(konst));
return 1;
});
}
int EmitterArray::EmitParameters(VMFunctionBuilder *build)
{
int paramcount = 0;
for (auto &func : emitters)
{
paramcount += func(build);
}
assert(paramcount == numparams);
return paramcount;
}

View file

@ -68,7 +68,6 @@ public:
size_t Emit(int opcode, int opa, int opb, int opc);
size_t Emit(int opcode, int opa, VM_SHALF opbc);
size_t Emit(int opcode, int opabc);
size_t EmitParamInt(int value);
size_t EmitLoadInt(int regnum, int value);
size_t EmitRetInt(int retnum, bool final, int value);
@ -154,4 +153,34 @@ public:
};
extern FFunctionBuildList FunctionBuildList;
//==========================================================================
//
// Function call parameter collector
//
//==========================================================================
extern int EncodeRegType(ExpEmit reg);
class EmitterArray
{
// std::function and TArray are not compatible so this has to use std::vector instead.
std::vector<std::function<int(VMFunctionBuilder *)>> emitters;
unsigned numparams = 0;
public:
void AddParameter(VMFunctionBuilder *build, FxExpression *operand);
void AddParameter(ExpEmit &emit, bool reference);
void AddParameterPointerConst(void *konst);
void AddParameterPointer(int index, bool konst);
void AddParameterFloatConst(double konst);
void AddParameterIntConst(int konst);
void AddParameterStringConst(const FString &konst);
int EmitParameters(VMFunctionBuilder *build);
unsigned Count() const
{
return numparams;
}
};
#endif