diff --git a/src/newgrf.cpp b/src/newgrf.cpp index 1c46e2f084..6b6b998c5d 100644 --- a/src/newgrf.cpp +++ b/src/newgrf.cpp @@ -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,15 +5638,93 @@ static void NewSpriteGroup(ByteReader *buf) } else { adjust.add_val = 0; adjust.divmod_val = 0; - if (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(); + } + + 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; + } } /* Continue reading var adjusts while bit 5 is set. */ diff --git a/src/newgrf_spritegroup.cpp b/src/newgrf_spritegroup.cpp index 73ce2751a1..217fdd2c0b 100644 --- a/src/newgrf_spritegroup.cpp +++ b/src/newgrf_spritegroup.cpp @@ -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 (adjust, scope, last_value, value); break; + case DSG_SIZE_WORD: return EvalAdjustT(adjust, scope, last_value, value); break; + case DSG_SIZE_DWORD: return EvalAdjustT(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[]; diff --git a/src/newgrf_spritegroup.h b/src/newgrf_spritegroup.h index be800eb660..623665ddb4 100644 --- a/src/newgrf_spritegroup.h +++ b/src/newgrf_spritegroup.h @@ -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 print); +uint32 EvaluateDeterministicSpriteGroupAdjust(DeterministicSpriteGroupSize size, const DeterministicSpriteGroupAdjust &adjust, ScopeResolver *scope, uint32 last_value, uint32 value); #endif /* NEWGRF_SPRITEGROUP_H */