VarAction2: Detect and replace ternary operator pattern
This commit is contained in:
@@ -5525,6 +5525,15 @@ static const SpriteGroup *CreateGroupFromGroupID(byte feature, byte setid, byte
|
||||
return new ResultSpriteGroup(spriteset_start, num_sprites);
|
||||
}
|
||||
|
||||
enum VarAction2AdjustInferenceFlags {
|
||||
VA2AIF_NONE = 0x00,
|
||||
|
||||
VA2AIF_SIGNED_NON_NEGATIVE = 0x01,
|
||||
VA2AIF_ONE_OR_ZERO = 0x02,
|
||||
VA2AIF_PREV_TERNARY = 0x04,
|
||||
};
|
||||
DECLARE_ENUM_AS_BIT_SET(VarAction2AdjustInferenceFlags)
|
||||
|
||||
/* Action 0x02 */
|
||||
static void NewSpriteGroup(ByteReader *buf)
|
||||
{
|
||||
@@ -5579,6 +5588,8 @@ static void NewSpriteGroup(ByteReader *buf)
|
||||
case 2: group->size = DSG_SIZE_DWORD; varsize = 4; break;
|
||||
}
|
||||
|
||||
VarAction2AdjustInferenceFlags inference = VA2AIF_NONE;
|
||||
|
||||
/* Loop through the var adjusts. Unfortunately we don't know how many we have
|
||||
* from the outset, so we shall have to keep reallocing. */
|
||||
do {
|
||||
@@ -5586,6 +5597,7 @@ static void NewSpriteGroup(ByteReader *buf)
|
||||
|
||||
/* The first var adjust doesn't have an operation specified, so we set it to add. */
|
||||
adjust.operation = group->adjusts.size() == 1 ? DSGA_OP_ADD : (DeterministicSpriteGroupAdjustOperation)buf->ReadByte();
|
||||
if (adjust.operation > DSGA_OP_END) adjust.operation = DSGA_OP_END;
|
||||
adjust.variable = buf->ReadByte();
|
||||
if (adjust.variable == 0x7E) {
|
||||
/* Link subroutine group */
|
||||
@@ -5626,14 +5638,92 @@ static void NewSpriteGroup(ByteReader *buf)
|
||||
} else {
|
||||
adjust.add_val = 0;
|
||||
adjust.divmod_val = 0;
|
||||
if (group->adjusts.size() > 1) {
|
||||
}
|
||||
|
||||
VarAction2AdjustInferenceFlags prev_inference = inference;
|
||||
inference = VA2AIF_NONE;
|
||||
|
||||
if ((prev_inference & VA2AIF_PREV_TERNARY) && adjust.variable == 0x1A && IsEvalAdjustUsableForConstantPropagation(adjust.operation)) {
|
||||
/* Propagate constant operation back into previous ternary */
|
||||
DeterministicSpriteGroupAdjust &prev = group->adjusts[group->adjusts.size() - 2];
|
||||
prev.and_mask = EvaluateDeterministicSpriteGroupAdjust(group->size, adjust, nullptr, prev.and_mask, UINT_MAX);
|
||||
prev.add_val = EvaluateDeterministicSpriteGroupAdjust(group->size, adjust, nullptr, prev.add_val, UINT_MAX);
|
||||
group->adjusts.pop_back();
|
||||
inference = prev_inference;
|
||||
} else if (adjust.type == DSGA_TYPE_NONE && group->adjusts.size() > 1) {
|
||||
/* Not the first adjustment */
|
||||
if (adjust.variable != 0x7E) {
|
||||
if (adjust.and_mask == 0 && IsEvalAdjustWithZeroRemovable(adjust.operation)) {
|
||||
/* Delete useless zero operations */
|
||||
group->adjusts.pop_back();
|
||||
inference = prev_inference;
|
||||
} else {
|
||||
switch (adjust.operation) {
|
||||
case DSGA_OP_SMIN:
|
||||
if (adjust.and_mask <= 1 && (prev_inference & VA2AIF_SIGNED_NON_NEGATIVE)) inference = VA2AIF_SIGNED_NON_NEGATIVE | VA2AIF_ONE_OR_ZERO;
|
||||
break;
|
||||
case DSGA_OP_UMIN:
|
||||
if (adjust.and_mask <= 1) inference = VA2AIF_SIGNED_NON_NEGATIVE | VA2AIF_ONE_OR_ZERO;
|
||||
break;
|
||||
case DSGA_OP_AND:
|
||||
if (adjust.and_mask <= 1) {
|
||||
inference = VA2AIF_SIGNED_NON_NEGATIVE | VA2AIF_ONE_OR_ZERO;
|
||||
} else if ((adjust.and_mask & (1 << ((varsize * 8) - 1))) == 0) {
|
||||
inference = VA2AIF_SIGNED_NON_NEGATIVE;
|
||||
}
|
||||
break;
|
||||
case DSGA_OP_OR:
|
||||
case DSGA_OP_XOR:
|
||||
if (adjust.and_mask <= 1) inference = prev_inference & (~VA2AIF_PREV_TERNARY);
|
||||
break;
|
||||
case DSGA_OP_MUL:
|
||||
if ((prev_inference & VA2AIF_ONE_OR_ZERO) && adjust.variable == 0x1A && adjust.shift_num == 0) {
|
||||
/* Found a ternary operator */
|
||||
adjust.operation = DSGA_OP_TERNARY;
|
||||
while (group->adjusts.size() > 1) {
|
||||
/* Merge with previous if applicable */
|
||||
const DeterministicSpriteGroupAdjust &prev = group->adjusts[group->adjusts.size() - 2];
|
||||
if (prev.type == DSGA_TYPE_NONE && prev.variable == 0x1A && prev.shift_num == 0 && prev.and_mask == 1) {
|
||||
if (prev.operation == DSGA_OP_XOR) {
|
||||
DeterministicSpriteGroupAdjust current = group->adjusts.back();
|
||||
group->adjusts.pop_back();
|
||||
group->adjusts.pop_back();
|
||||
std::swap(current.and_mask, current.add_val);
|
||||
group->adjusts.push_back(current);
|
||||
continue;
|
||||
} else if (prev.operation == DSGA_OP_SMIN || prev.operation == DSGA_OP_UMIN) {
|
||||
DeterministicSpriteGroupAdjust current = group->adjusts.back();
|
||||
group->adjusts.pop_back();
|
||||
group->adjusts.pop_back();
|
||||
group->adjusts.push_back(current);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
inference = VA2AIF_PREV_TERNARY;
|
||||
}
|
||||
break;
|
||||
case DSGA_OP_SCMP:
|
||||
inference = VA2AIF_SIGNED_NON_NEGATIVE;
|
||||
/* Convert to UCMP if possible to make other analysis operations easier */
|
||||
if ((prev_inference & VA2AIF_SIGNED_NON_NEGATIVE) && adjust.variable == 0x1A && adjust.shift_num == 0 && (adjust.and_mask & (1 << ((varsize * 8) - 1))) == 0) {
|
||||
adjust.operation = DSGA_OP_UCMP;
|
||||
}
|
||||
break;
|
||||
case DSGA_OP_UCMP:
|
||||
inference = VA2AIF_SIGNED_NON_NEGATIVE;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* First adjustment */
|
||||
if (adjust.and_mask == 1) {
|
||||
inference = VA2AIF_SIGNED_NON_NEGATIVE | VA2AIF_ONE_OR_ZERO;
|
||||
} else if ((adjust.and_mask & (1 << ((varsize * 8) - 1))) == 0) {
|
||||
inference = VA2AIF_SIGNED_NON_NEGATIVE;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -182,10 +182,21 @@ static U EvalAdjustT(const DeterministicSpriteGroupAdjust &adjust, ScopeResolver
|
||||
case DSGA_OP_SHL: return (uint32)(U)last_value << ((U)value & 0x1F); // Same behaviour as in ParamSet, mask 'value' to 5 bits, which should behave the same on all architectures.
|
||||
case DSGA_OP_SHR: return (uint32)(U)last_value >> ((U)value & 0x1F);
|
||||
case DSGA_OP_SAR: return (int32)(S)last_value >> ((U)value & 0x1F);
|
||||
case DSGA_OP_TERNARY: return (last_value != 0) ? value : adjust.add_val;
|
||||
default: return value;
|
||||
}
|
||||
}
|
||||
|
||||
uint32 EvaluateDeterministicSpriteGroupAdjust(DeterministicSpriteGroupSize size, const DeterministicSpriteGroupAdjust &adjust, ScopeResolver *scope, uint32 last_value, uint32 value)
|
||||
{
|
||||
switch (size) {
|
||||
case DSG_SIZE_BYTE: return EvalAdjustT<uint8, int8> (adjust, scope, last_value, value); break;
|
||||
case DSG_SIZE_WORD: return EvalAdjustT<uint16, int16>(adjust, scope, last_value, value); break;
|
||||
case DSG_SIZE_DWORD: return EvalAdjustT<uint32, int32>(adjust, scope, last_value, value); break;
|
||||
default: NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
static bool RangeHighComparator(const DeterministicSpriteGroupRange& range, uint32 value)
|
||||
{
|
||||
return range.high < value;
|
||||
@@ -601,6 +612,11 @@ void SpriteGroupDumper::DumpSpriteGroup(const SpriteGroup *sg, int padding, uint
|
||||
padding += 2;
|
||||
for (const auto &adjust : dsg->adjusts) {
|
||||
char *p = this->buffer;
|
||||
if (adjust.operation == DSGA_OP_TERNARY) {
|
||||
p += seprintf(p, lastof(this->buffer), "%*sTERNARY: true: %X, false: %X", padding, "", adjust.and_mask, adjust.add_val);
|
||||
this->print();
|
||||
continue;
|
||||
}
|
||||
p += seprintf(p, lastof(this->buffer), "%*svar: %X", padding, "", adjust.variable);
|
||||
if (adjust.variable >= 0x100) {
|
||||
extern const GRFVariableMapDefinition _grf_action2_remappable_variables[];
|
||||
|
@@ -161,6 +161,10 @@ enum DeterministicSpriteGroupAdjustOperation {
|
||||
DSGA_OP_SAR, ///< (signed) a >> b
|
||||
|
||||
DSGA_OP_END,
|
||||
|
||||
DSGA_OP_TERNARY = 0x80, ///< a == 0 ? b : c,
|
||||
|
||||
DSGA_OP_SPECIAL_END,
|
||||
};
|
||||
|
||||
inline bool IsEvalAdjustWithZeroRemovable(DeterministicSpriteGroupAdjustOperation op)
|
||||
@@ -181,6 +185,36 @@ inline bool IsEvalAdjustWithZeroRemovable(DeterministicSpriteGroupAdjustOperatio
|
||||
}
|
||||
}
|
||||
|
||||
inline bool IsEvalAdjustUsableForConstantPropagation(DeterministicSpriteGroupAdjustOperation op)
|
||||
{
|
||||
switch (op) {
|
||||
case DSGA_OP_ADD:
|
||||
case DSGA_OP_SUB:
|
||||
case DSGA_OP_SMIN:
|
||||
case DSGA_OP_SMAX:
|
||||
case DSGA_OP_UMIN:
|
||||
case DSGA_OP_UMAX:
|
||||
case DSGA_OP_SDIV:
|
||||
case DSGA_OP_SMOD:
|
||||
case DSGA_OP_UDIV:
|
||||
case DSGA_OP_UMOD:
|
||||
case DSGA_OP_MUL:
|
||||
case DSGA_OP_AND:
|
||||
case DSGA_OP_OR:
|
||||
case DSGA_OP_XOR:
|
||||
case DSGA_OP_ROR:
|
||||
case DSGA_OP_SCMP:
|
||||
case DSGA_OP_UCMP:
|
||||
case DSGA_OP_SHL:
|
||||
case DSGA_OP_SHR:
|
||||
case DSGA_OP_SAR:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct DeterministicSpriteGroupAdjust {
|
||||
DeterministicSpriteGroupAdjustOperation operation;
|
||||
@@ -457,5 +491,6 @@ struct ResolverObject {
|
||||
};
|
||||
|
||||
void DumpSpriteGroup(const SpriteGroup *sg, std::function<void(const char *)> print);
|
||||
uint32 EvaluateDeterministicSpriteGroupAdjust(DeterministicSpriteGroupSize size, const DeterministicSpriteGroupAdjust &adjust, ScopeResolver *scope, uint32 last_value, uint32 value);
|
||||
|
||||
#endif /* NEWGRF_SPRITEGROUP_H */
|
||||
|
Reference in New Issue
Block a user