file_sys: apply case-insensitive search to mods_path on GNU/Linux and macOS (#4312) (#4310)

The case-insensitive fallback search() in GetHostPath is only
invoked for patch_path and host_path, so mods whose file or folder
capitalization does not exactly match the guest path are silently
bypassed even when the files are present. Mirror the existing
search(patch_path) pass for mods_path, placed first to preserve
mod > patch > base precedence.

Co-authored-by: Matías Buzzo <matias@mbuzzo.com>
This commit is contained in:
Marcin Mikołajczyk 2026-04-27 17:07:05 +02:00 committed by GitHub
parent f98f1aac5a
commit fba374442c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 66 additions and 28 deletions

View File

@ -39,11 +39,7 @@ Id EmitFPFma64(EmitContext& ctx, IR::Inst* inst, Id a, Id b, Id c) {
return Decorate(ctx, inst, ctx.OpFma(ctx.F64[1], a, b, c));
}
Id EmitFPMax32(EmitContext& ctx, Id a, Id b, bool is_legacy) {
if (is_legacy) {
return ctx.OpNMax(ctx.F32[1], a, b);
}
Id EmitFPMax32(EmitContext& ctx, Id a, Id b) {
return ctx.OpFMax(ctx.F32[1], a, b);
}
@ -51,11 +47,7 @@ Id EmitFPMax64(EmitContext& ctx, Id a, Id b) {
return ctx.OpFMax(ctx.F64[1], a, b);
}
Id EmitFPMin32(EmitContext& ctx, Id a, Id b, bool is_legacy) {
if (is_legacy) {
return ctx.OpNMin(ctx.F32[1], a, b);
}
Id EmitFPMin32(EmitContext& ctx, Id a, Id b) {
return ctx.OpFMin(ctx.F32[1], a, b);
}

View File

@ -237,9 +237,9 @@ Id EmitFPAdd64(EmitContext& ctx, IR::Inst* inst, Id a, Id b);
Id EmitFPSub32(EmitContext& ctx, IR::Inst* inst, Id a, Id b);
Id EmitFPFma32(EmitContext& ctx, IR::Inst* inst, Id a, Id b, Id c);
Id EmitFPFma64(EmitContext& ctx, IR::Inst* inst, Id a, Id b, Id c);
Id EmitFPMax32(EmitContext& ctx, Id a, Id b, bool is_legacy = false);
Id EmitFPMax32(EmitContext& ctx, Id a, Id b);
Id EmitFPMax64(EmitContext& ctx, Id a, Id b);
Id EmitFPMin32(EmitContext& ctx, Id a, Id b, bool is_legacy = false);
Id EmitFPMin32(EmitContext& ctx, Id a, Id b);
Id EmitFPMin64(EmitContext& ctx, Id a, Id b);
Id EmitFPMinTri32(EmitContext& ctx, Id a, Id b, Id c);
Id EmitFPMaxTri32(EmitContext& ctx, Id a, Id b, Id c);

View File

@ -527,13 +527,25 @@ void Translator::V_MUL_I32_I24(const GcnInst& inst, bool is_signed) {
void Translator::V_MIN_F32(const GcnInst& inst, bool is_legacy) {
const IR::F32 src0{GetSrc<IR::F32>(inst.src[0])};
const IR::F32 src1{GetSrc<IR::F32>(inst.src[1])};
SetDst(inst.dst[0], ir.FPMin(src0, src1, is_legacy));
const IR::F32 fpmin = ir.FPMin(src0, src1);
const IR::F32 result =
is_legacy
? IR::F32{ir.Select(ir.LogicalOr(ir.FPIsNan(src0), ir.FPIsNan(src1)), src1, fpmin)}
: fpmin;
SetDst(inst.dst[0], result);
}
void Translator::V_MAX_F32(const GcnInst& inst, bool is_legacy) {
const IR::F32 src0{GetSrc<IR::F32>(inst.src[0])};
const IR::F32 src1{GetSrc<IR::F32>(inst.src[1])};
SetDst(inst.dst[0], ir.FPMax(src0, src1, is_legacy));
const IR::F32 fpmax = ir.FPMax(src0, src1);
const IR::F32 result =
is_legacy
? IR::F32{ir.Select(ir.LogicalOr(ir.FPIsNan(src0), ir.FPIsNan(src1)), src1, fpmax)}
: fpmax;
SetDst(inst.dst[0], result);
}
void Translator::V_MIN_I32(const GcnInst& inst) {

View File

@ -1386,35 +1386,29 @@ U1 IREmitter::FPUnordered(const F32F64& lhs, const F32F64& rhs) {
return LogicalOr(FPIsNan(lhs), FPIsNan(rhs));
}
F32F64 IREmitter::FPMax(const F32F64& lhs, const F32F64& rhs, bool is_legacy) {
F32F64 IREmitter::FPMax(const F32F64& lhs, const F32F64& rhs) {
if (lhs.Type() != rhs.Type()) {
UNREACHABLE_MSG("Mismatching types {} and {}", lhs.Type(), rhs.Type());
}
switch (lhs.Type()) {
case Type::F32:
return Inst<F32>(Opcode::FPMax32, lhs, rhs, is_legacy);
return Inst<F32>(Opcode::FPMax32, lhs, rhs);
case Type::F64:
if (is_legacy) {
UNREACHABLE_MSG("F64 cannot be used with LEGACY ops");
}
return Inst<F64>(Opcode::FPMax64, lhs, rhs);
default:
ThrowInvalidType(lhs.Type());
}
}
F32F64 IREmitter::FPMin(const F32F64& lhs, const F32F64& rhs, bool is_legacy) {
F32F64 IREmitter::FPMin(const F32F64& lhs, const F32F64& rhs) {
if (lhs.Type() != rhs.Type()) {
UNREACHABLE_MSG("Mismatching types {} and {}", lhs.Type(), rhs.Type());
}
switch (lhs.Type()) {
case Type::F32:
return Inst<F32>(Opcode::FPMin32, lhs, rhs, is_legacy);
return Inst<F32>(Opcode::FPMin32, lhs, rhs);
case Type::F64:
if (is_legacy) {
UNREACHABLE_MSG("F64 cannot be used with LEGACY ops");
}
return Inst<F64>(Opcode::FPMin64, lhs, rhs);
default:
ThrowInvalidType(lhs.Type());

View File

@ -259,8 +259,8 @@ public:
[[nodiscard]] U1 FPCmpClass32(const F32& value, const U32& op);
[[nodiscard]] U1 FPOrdered(const F32F64& lhs, const F32F64& rhs);
[[nodiscard]] U1 FPUnordered(const F32F64& lhs, const F32F64& rhs);
[[nodiscard]] F32F64 FPMax(const F32F64& lhs, const F32F64& rhs, bool is_legacy = false);
[[nodiscard]] F32F64 FPMin(const F32F64& lhs, const F32F64& rhs, bool is_legacy = false);
[[nodiscard]] F32F64 FPMax(const F32F64& lhs, const F32F64& rhs);
[[nodiscard]] F32F64 FPMin(const F32F64& lhs, const F32F64& rhs);
[[nodiscard]] F32F64 FPMinTri(const F32F64& a, const F32F64& b, const F32F64& c);
[[nodiscard]] F32F64 FPMaxTri(const F32F64& a, const F32F64& b, const F32F64& c);
[[nodiscard]] F32F64 FPMedTri(const F32F64& a, const F32F64& b, const F32F64& c);

View File

@ -236,9 +236,9 @@ OPCODE(FPAdd64, F64, F64,
OPCODE(FPSub32, F32, F32, F32, )
OPCODE(FPFma32, F32, F32, F32, F32, )
OPCODE(FPFma64, F64, F64, F64, F64, )
OPCODE(FPMax32, F32, F32, F32, U1, )
OPCODE(FPMax32, F32, F32, F32, )
OPCODE(FPMax64, F64, F64, F64, )
OPCODE(FPMin32, F32, F32, F32, U1, )
OPCODE(FPMin32, F32, F32, F32, )
OPCODE(FPMin64, F64, F64, F64, )
OPCODE(FPMinTri32, F32, F32, F32, F32, )
OPCODE(FPMaxTri32, F32, F32, F32, F32, )

View File

@ -121,3 +121,43 @@ TEST_F(GcnTest, sub_f16) {
EXPECT_TRUE(result.has_value());
EXPECT_EQ(*result, F16x2{half(-1.0f)}); //confirmed with neo
}
TEST_F(GcnTest, mul_legacy_nan) {
auto runner = gcn_test::Runner::instance().value();
auto spirv = TranslateToSpirv(VOP2(OpcodeVOP2::V_MUL_LEGACY_F32, VOperand8::V0, SOperand9::V0, VOperand8::V1).Get());
auto result = runner->run<u32>(spirv, std::array{u32(0), u32(0x7fc00000)});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(*result, 0);
}
TEST_F(GcnTest, mul_nan) {
auto runner = gcn_test::Runner::instance().value();
auto spirv = TranslateToSpirv(VOP2(OpcodeVOP2::V_MUL_F32, VOperand8::V0, SOperand9::V0, VOperand8::V1).Get());
auto result = runner->run<float>(spirv, std::array{u32(0), u32(0x7fc00000)});
EXPECT_TRUE(result.has_value());
EXPECT_TRUE(std::isnan(*result));
}
TEST_F(GcnTest, min_legacy_nan) {
auto runner = gcn_test::Runner::instance().value();
auto spirv = TranslateToSpirv(VOP2(OpcodeVOP2::V_MIN_LEGACY_F32, VOperand8::V0, SOperand9::V0, VOperand8::V1).Get());
auto result = runner->run<u32>(spirv, std::array{u32(0), u32(0x7fc00000)});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(*result, 0x7fc00000);
}
TEST_F(GcnTest, min_nan) {
auto runner = gcn_test::Runner::instance().value();
auto spirv = TranslateToSpirv(VOP2(OpcodeVOP2::V_MIN_F32, VOperand8::V0, SOperand9::V0, VOperand8::V1).Get());
auto result = runner->run<float>(spirv, std::array{u32(0), u32(0x7fc00000)});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(*result, 0);
}