diff --git a/src/core/DynaRec_aa64/instructions.cc b/src/core/DynaRec_aa64/instructions.cc index 455b8d053..954e2e4c6 100644 --- a/src/core/DynaRec_aa64/instructions.cc +++ b/src/core/DynaRec_aa64/instructions.cc @@ -493,10 +493,12 @@ void DynaRecCPU::recLWL(uint32_t code) { const uint32_t alignedAddress = address & ~3; const uint32_t mask = LWL_MASK[address & 3]; const auto shift = LWL_SHIFT[address & 3]; + const uint32_t msanReadMask = 0b1111 >> (3 - (address & 3)); const uint32_t previousValue = m_gprs[_Rt_].val; gen.Mov(arg1, alignedAddress); // Address in arg1 - call(read32Wrapper); // Read value returned in w0 + gen.Mov(arg2, msanReadMask); // Mask in arg2 + call(read32MaskedWrapper); // Read value returned in w0 if (_Rt_) { allocateReg(_Rt_); // Allocate $rt with writeback @@ -510,8 +512,10 @@ void DynaRecCPU::recLWL(uint32_t code) { const uint32_t alignedAddress = address & ~3; const uint32_t mask = LWL_MASK[address & 3]; const auto shift = LWL_SHIFT[address & 3]; + const uint32_t msanReadMask = 0b1111 >> (3 - (address & 3)); gen.Mov(arg1, alignedAddress); // Address in arg1 + gen.Mov(arg2, msanReadMask); // Mask in arg2 call(read32Wrapper); // Read value returned in w0 if (_Rt_) { @@ -525,9 +529,14 @@ void DynaRecCPU::recLWL(uint32_t code) { const uint32_t previousValue = m_gprs[_Rt_].val; allocateReg(_Rs_); // Allocate address reg - gen.moveAndAdd(arg1, m_gprs[_Rs_].allocatedReg, _Imm_); // Address in arg1 - gen.And(arg1, arg1, ~3); // Force align it - call(read32Wrapper); // Read from the aligned address, result in w0 + gen.moveAndAdd(x3, m_gprs[_Rs_].allocatedReg, _Imm_); // Address in arg1 + gen.And(arg1, w3, ~3); // Force align it + gen.And(arg2, w3, 3); // Mask in arg2 + gen.Mov(w5, 3); + gen.Sub(arg2, w5, arg2); + gen.Mov(w5, 0b1111); + gen.Lsr(arg2, w5, arg2); + call(read32MaskedWrapper); // Read from the aligned address, result in w0 if (_Rt_) { // The call might have flushed $rs, so we need to allocate it again, and also allocate $rt @@ -545,9 +554,14 @@ void DynaRecCPU::recLWL(uint32_t code) { } } else { // Nothing is constant allocateReg(_Rs_); // Allocate address reg - gen.moveAndAdd(arg1, m_gprs[_Rs_].allocatedReg, _Imm_); // Address in arg1 - gen.And(arg1, arg1, ~3); // Force align it - call(read32Wrapper); // Read from the aligned address, result in w0 + gen.moveAndAdd(x3, m_gprs[_Rs_].allocatedReg, _Imm_); // Address in arg1 + gen.And(arg1, w3, ~3); // Force align it + gen.And(arg2, w3, 3); // Mask in arg2 + gen.Mov(w5, 3); + gen.Sub(arg2, w5, arg2); + gen.Mov(w5, 0b1111); + gen.Lsr(arg2, w5, arg2); + call(read32MaskedWrapper); // Read from the aligned address, result in w0 if (_Rt_) { // The call might have flushed $rs, so we need to allocate it again, and also allocate $rt @@ -575,10 +589,12 @@ void DynaRecCPU::recLWR(uint32_t code) { const uint32_t alignedAddress = address & ~3; const uint32_t mask = LWR_MASK[address & 3]; const auto shift = LWR_SHIFT[address & 3]; + const uint32_t msanReadMask = (0b1111 << (address & 3)) & 0b1111; const uint32_t previousValue = m_gprs[_Rt_].val; gen.Mov(arg1, alignedAddress); // Address in arg1 (w0) - call(read32Wrapper); // Read value returned in w0 + gen.Mov(arg2, msanReadMask); // Mask in arg2 + call(read32MaskedWrapper); // Read value returned in w0 if (_Rt_) { allocateReg(_Rt_); // Allocate $rt with writeback @@ -592,9 +608,11 @@ void DynaRecCPU::recLWR(uint32_t code) { const uint32_t alignedAddress = address & ~3; const uint32_t mask = LWR_MASK[address & 3]; const auto shift = LWR_SHIFT[address & 3]; + const uint32_t msanReadMask = (0b1111 << (address & 3)) & 0b1111; gen.Mov(arg1, alignedAddress); // Address in arg1 - call(read32Wrapper); // Read value returned in w0 + gen.Mov(arg2, msanReadMask); // Mask in arg2 + call(read32MaskedWrapper); // Read value returned in w0 if (_Rt_) { allocateReg(_Rt_); // Allocate $rt with writeback @@ -607,9 +625,13 @@ void DynaRecCPU::recLWR(uint32_t code) { const uint32_t previousValue = m_gprs[_Rt_].val; allocateReg(_Rs_); // Allocate address reg - gen.moveAndAdd(arg1, m_gprs[_Rs_].allocatedReg, _Imm_); // Address in arg1 - gen.And(arg1, arg1, ~3); // Force align it - call(read32Wrapper); // Read from the aligned address, result in w0 + gen.moveAndAdd(x3, m_gprs[_Rs_].allocatedReg, _Imm_); // Address in arg1 + gen.And(arg1, w3, ~3); // Force align it + gen.And(arg2, w3, 3); // Mask in arg2 + gen.Mov(w5, 0b1111); + gen.Lsl(arg2, w5, arg2); + gen.And(arg2, arg2, 0b1111); + call(read32MaskedWrapper); // Read from the aligned address, result in w0 if (_Rt_) { // The call might have flushed $rs, so we need to allocate it again, and also allocate $rt @@ -627,9 +649,13 @@ void DynaRecCPU::recLWR(uint32_t code) { } } else { // Nothing is constant allocateReg(_Rs_); // Allocate address reg - gen.moveAndAdd(arg1, m_gprs[_Rs_].allocatedReg, _Imm_); // Address in arg1 - gen.And(arg1, arg1, ~3); // Force align it - call(read32Wrapper); // Read from the aligned address, result in eax + gen.moveAndAdd(x3, m_gprs[_Rs_].allocatedReg, _Imm_); // Address in arg1 + gen.And(arg1, w3, ~3); // Force align it + gen.And(arg2, w3, 3); // Mask in arg2 + gen.Mov(w5, 0b1111); + gen.Lsl(arg2, w5, arg2); + gen.And(arg2, arg2, 0b1111); + call(read32MaskedWrapper); // Read from the aligned address, result in eax if (_Rt_) { // The call might have flushed $rs, so we need to allocate it again, and also allocate $rt @@ -1358,35 +1384,42 @@ void DynaRecCPU::recSWL(uint32_t code) { const uint32_t alignedAddress = address & ~3; const uint32_t mask = SWL_MASK[address & 3]; const auto shift = SWL_SHIFT[address & 3]; + const uint32_t msanWriteMask = 0b1111 >> (3 - (address & 3)); gen.Mov(arg1, alignedAddress); // Address in arg1 (w0) - call(read32Wrapper); + gen.Mov(arg2, 0x0); // Mask in arg2 + call(read32MaskedWrapper); gen.andImm(arg2, w0, mask); // Mask read value gen.Mov(w2, m_gprs[_Rt_].val >> shift); gen.Orr(arg2, arg2, w2); // Shift $rt and or with read value gen.Mov(arg1, alignedAddress); // Address in arg2 again - call(write32Wrapper); + gen.Mov(arg3, msanWriteMask); // Mask in arg3 + call(write32MaskedWrapper); } else if (m_gprs[_Rs_].isConst()) { // Only address is constant const uint32_t address = m_gprs[_Rs_].val + _Imm_; const uint32_t alignedAddress = address & ~3; const uint32_t mask = SWL_MASK[address & 3]; const auto shift = SWL_SHIFT[address & 3]; + const uint32_t msanWriteMask = 0b1111 >> (3 - (address & 3)); gen.Mov(arg1, alignedAddress); // Address in arg1 (w0) - call(read32Wrapper); + gen.Mov(arg2, 0x0); // Mask in arg2 + call(read32MaskedWrapper); gen.andImm(w0, w0, mask); // Mask read value allocateReg(_Rt_); // Allocate $rt // Value to write back to memory in $arg2 gen.Orr(arg2, w0, Operand(m_gprs[_Rt_].allocatedReg, LSR, shift)); gen.Mov(arg1, alignedAddress); // Aligned address in arg1 again - call(write32Wrapper); // Write back + gen.Mov(arg3, msanWriteMask); // Mask in arg3 + call(write32MaskedWrapper); // Write back } else if (m_gprs[_Rt_].isConst()) { // Only previous rt value is constant allocateReg(_Rs_); // Allocate address reg gen.moveAndAdd(arg1, m_gprs[_Rs_].allocatedReg, _Imm_); // Address in arg1 gen.And(arg1, arg1, ~3); // Force align it - call(read32Wrapper); // Read from the aligned address, result in w0 + gen.Mov(arg2, 0x0); // Mask in arg2 + call(read32MaskedWrapper); // Read from the aligned address, result in w0 // The call might have flushed $rs, so we need to allocate it again, and also allocate $rt allocateReg(_Rs_); @@ -1403,12 +1436,17 @@ void DynaRecCPU::recSWL(uint32_t code) { gen.And(x0, x0, Operand(x3, LSR, 32)); // Mask read value gen.Orr(arg2, arg2, w0); gen.Mov(arg1, w5); - call(write32Wrapper); + gen.Mov(w5, 3); + gen.Sub(arg3, w5, w2); // Mask in arg3 + gen.Mov(w5, 0b1111); + gen.Lsr(arg3, w5, arg3); + call(write32MaskedWrapper); } else { // Nothing is constant allocateReg(_Rs_); // Allocate address reg gen.moveAndAdd(arg1, m_gprs[_Rs_].allocatedReg, _Imm_); // Address in arg1 gen.And(arg1, arg1, ~3); // Force align it - call(read32Wrapper); // Read from the aligned address, result in w0 + gen.Mov(arg2, 0x0); // Mask in arg2 + call(read32MaskedWrapper); // Read from the aligned address, result in w0 // The call might have flushed $rs, so we need to allocate it again, and also allocate $rt alloc_rt_rs(code); @@ -1425,7 +1463,11 @@ void DynaRecCPU::recSWL(uint32_t code) { gen.And(x0, x0, Operand(x3, LSR, 32)); // Mask read value gen.Orr(arg2, arg2, w0); // arg2 = value to write to memory gen.Mov(arg1, w5); - call(write32Wrapper); + gen.Mov(w5, 3); + gen.Sub(arg3, w5, w2); // Mask in arg3 + gen.Mov(w5, 0b1111); + gen.Lsr(arg3, w5, arg3); + call(write32MaskedWrapper); } } @@ -1439,35 +1481,42 @@ void DynaRecCPU::recSWR(uint32_t code) { const uint32_t alignedAddress = address & ~3; const uint32_t mask = SWR_MASK[address & 3]; const auto shift = SWR_SHIFT[address & 3]; + const uint32_t msanWriteMask = (0b1111 << (address & 3)) & 0b1111; gen.Mov(arg1, alignedAddress); // Address in arg1 (w0) - call(read32Wrapper); + gen.Mov(arg2, 0x0); // Mask in arg2 + call(read32MaskedWrapper); gen.andImm(arg2, w0, mask); // Mask read value gen.Mov(w2, m_gprs[_Rt_].val << shift); gen.Orr(arg2, arg2, w2); // Shift $rt and or with read value gen.Mov(arg1, alignedAddress); // Address in arg2 again - call(write32Wrapper); + gen.Mov(arg3, msanWriteMask); // Mask in arg3 + call(write32MaskedWrapper); } else if (m_gprs[_Rs_].isConst()) { // Only address is constant const uint32_t address = m_gprs[_Rs_].val + _Imm_; const uint32_t alignedAddress = address & ~3; const uint32_t mask = SWR_MASK[address & 3]; const auto shift = SWR_SHIFT[address & 3]; + const uint32_t msanWriteMask = (0b1111 << (address & 3)) & 0b1111; gen.Mov(arg1, alignedAddress); // Address in arg1 (w0) - call(read32Wrapper); + gen.Mov(arg2, 0x0); // Mask in arg2 + call(read32MaskedWrapper); gen.andImm(w0, w0, mask); // Mask read value allocateReg(_Rt_); // Allocate $rt gen.Lsl(arg2, m_gprs[_Rt_].allocatedReg, shift); // arg2 = shifted $rt gen.Orr(arg2, arg2, w0); // Or with read value gen.Mov(arg1, alignedAddress); // Aligned address in arg1 again - call(write32Wrapper); // Write back + gen.Mov(arg3, msanWriteMask); // Mask in arg3 + call(write32MaskedWrapper); // Write back } else if (m_gprs[_Rt_].isConst()) { // Only previous rt value is constant allocateReg(_Rs_); // Allocate address reg gen.moveAndAdd(arg1, m_gprs[_Rs_].allocatedReg, _Imm_); // Address in arg1 gen.And(arg1, arg1, ~3); // Force align it - call(read32Wrapper); // Read from the aligned address, result in w0 + gen.Mov(arg2, 0x0); // Mask in arg2 + call(read32MaskedWrapper); // Read from the aligned address, result in w0 // The call might have flushed $rs, so we need to allocate it again, and also allocate $rt allocateReg(_Rs_); @@ -1485,12 +1534,16 @@ void DynaRecCPU::recSWR(uint32_t code) { gen.And(x0, x0, Operand(x3, LSR, 32)); // Mask read value gen.Orr(arg2, arg2, w0); gen.Mov(arg1, w5); - call(write32Wrapper); + gen.Mov(arg3, 0b1111); + gen.Lsl(arg3, arg3, w2); // Mask in arg3 + gen.And(arg3, arg3, 0b1111); + call(write32MaskedWrapper); } else { // Nothing is constant allocateReg(_Rs_); // Allocate address reg gen.moveAndAdd(arg1, m_gprs[_Rs_].allocatedReg, _Imm_); // Address in arg1 gen.And(arg1, arg1, ~3); // Force align it - call(read32Wrapper); // Read from the aligned address, result in w0 + gen.Mov(arg2, 0x0); // Mask in arg2 + call(read32MaskedWrapper); // Read from the aligned address, result in w0 // The call might have flushed $rs, so we need to allocate it again, and also allocate $rt alloc_rt_rs(code); @@ -1508,7 +1561,10 @@ void DynaRecCPU::recSWR(uint32_t code) { gen.And(x0, x0, Operand(x3, LSR, 32)); // Mask read value gen.Orr(arg2, arg2, w0); // Or with read value gen.Mov(arg1, w5); - call(write32Wrapper); + gen.Mov(arg3, 0b1111); + gen.Lsl(arg3, arg3, w2); // Mask in arg3 + gen.And(arg3, arg3, 0b1111); + call(write32MaskedWrapper); } } diff --git a/src/core/DynaRec_aa64/recompiler.h b/src/core/DynaRec_aa64/recompiler.h index 64c89390f..02a500711 100644 --- a/src/core/DynaRec_aa64/recompiler.h +++ b/src/core/DynaRec_aa64/recompiler.h @@ -83,6 +83,9 @@ static uint8_t read8Wrapper(uint32_t address) { return PCSX::g_emulator->m_mem->read8(address); } static uint16_t read16Wrapper(uint32_t address) { return PCSX::g_emulator->m_mem->read16(address); } static uint32_t read32Wrapper(uint32_t address) { return PCSX::g_emulator->m_mem->read32(address); } +static uint32_t read32MaskedWrapper(uint32_t address, const uint32_t msanSubBitmask) { + return PCSX::g_emulator->m_mem->read32(address, PCSX::Memory::ReadType::Data, msanSubBitmask); +} static void SPU_writeRegisterWrapper(uint32_t addr, uint16_t value) { PCSX::g_emulator->m_spu->writeRegister(addr, value); @@ -91,6 +94,9 @@ static void SPU_writeRegisterWrapper(uint32_t addr, uint16_t value) { static void write8Wrapper(uint32_t address, uint32_t value) { PCSX::g_emulator->m_mem->write8(address, value); } static void write16Wrapper(uint32_t address, uint32_t value) { PCSX::g_emulator->m_mem->write16(address, value); } static void write32Wrapper(uint32_t address, uint32_t value) { PCSX::g_emulator->m_mem->write32(address, value); } +static void write32MaskedWrapper(uint32_t address, uint32_t value, const uint32_t msanSubBitmask) { + PCSX::g_emulator->m_mem->write32(address, value, msanSubBitmask); +} using DynarecCallback = void (*)(); // A function pointer to JIT-emitted code diff --git a/src/core/DynaRec_x64/instructions.cc b/src/core/DynaRec_x64/instructions.cc index 42ce460cb..169bd0d4c 100644 --- a/src/core/DynaRec_x64/instructions.cc +++ b/src/core/DynaRec_x64/instructions.cc @@ -802,14 +802,21 @@ void DynaRecCPU::recLWL(uint32_t code) { if (_Rt_ == 0) { // If $rt == 0, just execute the read in case it has side-effects, then return if (m_gprs[_Rs_].isConst()) { const uint32_t address = m_gprs[_Rs_].val + _Imm_; - gen.mov(arg2, address & ~3); // Aligned address in arg2 + gen.mov(eax, address & ~3); // Aligned address in arg1 + gen.mov(ebx, address & 3); } else { - allocateReg(_Rs_); // Allocate address reg - gen.moveAndAdd(arg2, m_gprs[_Rs_].allocatedReg, _Imm_); // Address in arg2 - gen.and_(arg2, ~3); // Force align it - } - - callMemoryFunc(&PCSX::Memory::read32); // Read from the aligned address + allocateReg(_Rs_); // Allocate address reg + gen.moveAndAdd(eax, m_gprs[_Rs_].allocatedReg, _Imm_); // Address in arg1 + gen.mov(ebx, eax); + gen.and_(eax, ~3); // Force align it + gen.and_(ebx, 3); + } + gen.mov(ecx, 3); + gen.sub(ecx, ebx); + gen.mov(arg2, 0b1111); + gen.shr(arg2, cl); // Mask in arg2 + gen.mov(arg1, eax); + call(read32MaskedWrapper); // Read from the aligned address return; } @@ -823,10 +830,12 @@ void DynaRecCPU::recLWL(uint32_t code) { const uint32_t alignedAddress = address & ~3; const uint32_t mask = LWL_MASK[address & 3]; const auto shift = LWL_SHIFT[address & 3]; + const uint32_t msanReadMask = 0b1111 >> (3 - (address & 3)); const uint32_t previousValue = m_gprs[_Rt_].val; - gen.mov(arg2, alignedAddress); // Address in arg2 - callMemoryFunc(&PCSX::Memory::read32); + gen.mov(arg1, alignedAddress); // Address in arg1 + gen.mov(arg2, msanReadMask); // Mask in arg2 + call(read32MaskedWrapper); allocateReg(_Rt_); // Allocate $rt with writeback m_gprs[_Rt_].setWriteback(true); @@ -838,9 +847,11 @@ void DynaRecCPU::recLWL(uint32_t code) { const uint32_t alignedAddress = address & ~3; const uint32_t mask = LWL_MASK[address & 3]; const auto shift = LWL_SHIFT[address & 3]; + const uint32_t msanReadMask = 0b1111 >> (3 - (address & 3)); - gen.mov(arg2, alignedAddress); // Address in arg2 - callMemoryFunc(&PCSX::Memory::read32); + gen.mov(arg1, alignedAddress); // Address in arg1 + gen.mov(arg2, msanReadMask); // Mask in arg2 + call(read32MaskedWrapper); allocateReg(_Rt_); // Allocate $rt with writeback m_gprs[_Rt_].setWriteback(true); @@ -851,9 +862,16 @@ void DynaRecCPU::recLWL(uint32_t code) { const uint32_t previousValue = m_gprs[_Rt_].val; allocateReg(_Rs_); // Allocate address reg - gen.moveAndAdd(arg2, m_gprs[_Rs_].allocatedReg, _Imm_); // Address in arg2 - gen.and_(arg2, ~3); // Force align it - callMemoryFunc(&PCSX::Memory::read32); // Read from the aligned address, result in eax + gen.moveAndAdd(eax, m_gprs[_Rs_].allocatedReg, _Imm_); + gen.mov(ebx, eax); + gen.and_(ebx, 3); + gen.mov(ecx, 3); + gen.sub(ecx, ebx); + gen.mov(arg2, 0b1111); + gen.shr(arg2, cl); // Mask in arg2 + gen.mov(arg1, eax); // Address in arg1 + gen.and_(arg1, ~3); // Force align it + call(read32MaskedWrapper); // Read from the aligned address, result in eax // The call might have flushed $rs, so we need to allocate it again, and also allocate $rt alloc_rt_rs(code); @@ -871,9 +889,16 @@ void DynaRecCPU::recLWL(uint32_t code) { gen.or_(m_gprs[_Rt_].allocatedReg, eax); // Merge with newly read value } else { // Nothing is constant allocateReg(_Rs_); // Allocate address reg - gen.moveAndAdd(arg2, m_gprs[_Rs_].allocatedReg, _Imm_); // Address in arg2 - gen.and_(arg2, ~3); // Force align it - callMemoryFunc(&PCSX::Memory::read32); // Read from the aligned address, result in eax + gen.moveAndAdd(eax, m_gprs[_Rs_].allocatedReg, _Imm_); + gen.mov(ebx, eax); + gen.and_(ebx, 3); + gen.mov(ecx, 3); + gen.sub(ecx, ebx); + gen.mov(arg2, 0b1111); + gen.shr(arg2, cl); // Mask in arg2 + gen.mov(arg1, eax); // Address in arg1 + gen.and_(arg1, ~3); // Force align it + call(read32MaskedWrapper); // Read from the aligned address, result in eax // The call might have flushed $rs, so we need to allocate it again, and also allocate $rt alloc_rt_rs(code); @@ -895,14 +920,20 @@ void DynaRecCPU::recLWR(uint32_t code) { if (_Rt_ == 0) { // If $rt == 0, just execute the read in case it has side-effects, then return if (m_gprs[_Rs_].isConst()) { const uint32_t address = m_gprs[_Rs_].val + _Imm_; - gen.mov(arg2, address & ~3); // Aligned address in arg2 + gen.mov(eax, address & ~3); // Aligned address in arg1 + gen.mov(ecx, address & 3); } else { allocateReg(_Rs_); // Allocate address reg - gen.moveAndAdd(arg2, m_gprs[_Rs_].allocatedReg, _Imm_); // Address in arg2 - gen.and_(arg2, ~3); // Force align it - } - - callMemoryFunc(&PCSX::Memory::read32); // Read from the aligned address + gen.moveAndAdd(eax, m_gprs[_Rs_].allocatedReg, _Imm_); // Address in arg1 + gen.mov(ecx, eax); + gen.and_(eax, ~3); // Force align it + gen.and_(ecx, 3); + } + gen.mov(arg2, 0b1111); + gen.shr(arg2, al); + gen.and_(arg2, 0b1111); // Mask in arg2 + gen.mov(arg1, eax); + call(read32MaskedWrapper); // Read from the aligned address return; } @@ -916,10 +947,12 @@ void DynaRecCPU::recLWR(uint32_t code) { const uint32_t alignedAddress = address & ~3; const uint32_t mask = LWR_MASK[address & 3]; const auto shift = LWR_SHIFT[address & 3]; + const uint32_t msanReadMask = (0b1111 << (address & 3)) & 0b1111; const uint32_t previousValue = m_gprs[_Rt_].val; - gen.mov(arg2, alignedAddress); // Address in arg2 - callMemoryFunc(&PCSX::Memory::read32); + gen.mov(arg1, alignedAddress); // Address in arg1 + gen.mov(arg2, msanReadMask); // Mask in arg2 + call(read32MaskedWrapper); allocateReg(_Rt_); // Allocate $rt with writeback m_gprs[_Rt_].setWriteback(true); @@ -931,9 +964,11 @@ void DynaRecCPU::recLWR(uint32_t code) { const uint32_t alignedAddress = address & ~3; const uint32_t mask = LWR_MASK[address & 3]; const auto shift = LWR_SHIFT[address & 3]; + const uint32_t msanReadMask = (0b1111 << (address & 3)) & 0b1111; - gen.mov(arg2, alignedAddress); // Address in arg2 - callMemoryFunc(&PCSX::Memory::read32); + gen.mov(arg1, alignedAddress); // Address in arg1 + gen.mov(arg2, msanReadMask); // Mask in arg2 + call(read32MaskedWrapper); allocateReg(_Rt_); // Allocate $rt with writeback m_gprs[_Rt_].setWriteback(true); @@ -944,9 +979,15 @@ void DynaRecCPU::recLWR(uint32_t code) { const uint32_t previousValue = m_gprs[_Rt_].val; allocateReg(_Rs_); // Allocate address reg - gen.moveAndAdd(arg2, m_gprs[_Rs_].allocatedReg, _Imm_); // Address in arg2 - gen.and_(arg2, ~3); // Force align it - callMemoryFunc(&PCSX::Memory::read32); // Read from the aligned address, result in eax + gen.moveAndAdd(ebx, m_gprs[_Rs_].allocatedReg, _Imm_); // Address in arg1 + gen.mov(ecx, ebx); + gen.and_(ecx, 3); + gen.mov(arg2, 0b1111); + gen.shl(arg2, cl); + gen.and_(arg2, 0b1111); // Mask in arg2 + gen.mov(arg1, ebx); + gen.and_(arg1, ~3); // Force align it + call(read32MaskedWrapper); // Read from the aligned address, result in eax // The call might have flushed $rs, so we need to allocate it again, and also allocate $rt alloc_rt_rs(code); @@ -964,9 +1005,15 @@ void DynaRecCPU::recLWR(uint32_t code) { gen.or_(m_gprs[_Rt_].allocatedReg, eax); // Merge with newly read value } else { // Nothing is constant allocateReg(_Rs_); // Allocate address reg - gen.moveAndAdd(arg2, m_gprs[_Rs_].allocatedReg, _Imm_); // Address in arg2 - gen.and_(arg2, ~3); // Force align it - callMemoryFunc(&PCSX::Memory::read32); // Read from the aligned address, result in eax + gen.moveAndAdd(ebx, m_gprs[_Rs_].allocatedReg, _Imm_); // Address in arg1 + gen.mov(ecx, ebx); + gen.and_(ecx, 3); + gen.mov(arg2, 0b1111); + gen.shl(arg2, cl); + gen.and_(arg2, 0b1111); // Mask in arg2 + gen.mov(arg1, ebx); + gen.and_(arg1, ~3); // Force align it + call(read32MaskedWrapper); // Read from the aligned address, result in eax // The call might have flushed $rs, so we need to allocate it again, and also allocate $rt alloc_rt_rs(code); @@ -1143,23 +1190,28 @@ void DynaRecCPU::recSWL(uint32_t code) { const uint32_t alignedAddress = address & ~3; const uint32_t mask = SWL_MASK[address & 3]; const auto shift = SWL_SHIFT[address & 3]; + const uint32_t msanWriteMask = 0b1111 >> (3 - (address & 3)); gen.mov(arg1, alignedAddress); // Address in arg1 - call(read32Wrapper); + gen.mov(arg2, 0x0); // Mask in arg2 + call(read32MaskedWrapper); gen.andImm(eax, eax, mask); // Mask read value gen.or_(eax, m_gprs[_Rt_].val >> shift); // Shift $rt and or with read value - gen.mov(arg1, alignedAddress); // Address in arg2 again + gen.mov(arg1, alignedAddress); // Address in arg1 again gen.mov(arg2, eax); // Address to write to in arg2 - call(write32Wrapper); + gen.mov(arg3, msanWriteMask); // Mask in arg3 + call(write32MaskedWrapper); } else if (m_gprs[_Rs_].isConst()) { // Only address is constant const uint32_t address = m_gprs[_Rs_].val + _Imm_; const uint32_t alignedAddress = address & ~3; const uint32_t mask = SWL_MASK[address & 3]; const auto shift = SWL_SHIFT[address & 3]; + const uint32_t msanWriteMask = 0b1111 >> (3 - (address & 3)); gen.mov(arg1, alignedAddress); // Address in arg1 - call(read32Wrapper); + gen.mov(arg2, 0x0); // Mask in arg2 + call(read32MaskedWrapper); gen.andImm(eax, eax, mask); // Mask read value gen.mov(arg1, alignedAddress); // Aligned address in arg1 again @@ -1167,12 +1219,14 @@ void DynaRecCPU::recSWL(uint32_t code) { gen.mov(arg2, m_gprs[_Rt_].allocatedReg); // Move rt to arg2 gen.shr(arg2, shift); // Shift rt value gen.or_(arg2, eax); // Or with read value - call(write32Wrapper); // Write back + gen.mov(arg3, msanWriteMask); // Mask in arg3 + call(write32MaskedWrapper); // Write back } else if (m_gprs[_Rt_].isConst()) { // Only previous rt value is constant allocateReg(_Rs_); // Allocate address reg gen.moveAndAdd(arg1, m_gprs[_Rs_].allocatedReg, _Imm_); // Address in arg1 gen.and_(arg1, ~3); // Force align it - call(read32Wrapper); // Read from the aligned address, result in eax + gen.mov(arg2, 0x0); // Mask in arg2 + call(read32MaskedWrapper); // Read from the aligned address, result in eax // The call might have flushed $rs, so we need to allocate it again, and also allocate $rt allocateReg(_Rs_); @@ -1201,12 +1255,17 @@ void DynaRecCPU::recSWL(uint32_t code) { gen.mov(arg1, arg4); } - call(write32Wrapper); + // gen.mov(rax, 3); + // gen.sub(rax, edx); + gen.mov(arg3, 0b1111); + // gen.shr(arg3, al); // Mask in arg3 + call(write32MaskedWrapper); } else { // Nothing is constant allocateReg(_Rs_); // Allocate address reg gen.moveAndAdd(arg1, m_gprs[_Rs_].allocatedReg, _Imm_); // Address in arg1 gen.and_(arg1, ~3); // Force align it - call(read32Wrapper); // Read from the aligned address, result in eax + gen.mov(arg2, 0x0); // Mask in arg2 + call(read32MaskedWrapper); // Read from the aligned address, result in eax // The call might have flushed $rs, so we need to allocate it again, and also allocate $rt alloc_rt_rs(code); @@ -1235,7 +1294,11 @@ void DynaRecCPU::recSWL(uint32_t code) { gen.mov(arg1, arg4); } - call(write32Wrapper); + // gen.mov(rax, 3); + // gen.sub(rax, edx); + gen.mov(arg3, 0b1111); + // gen.shr(arg3, al); // Mask in arg3 + call(write32MaskedWrapper); } } @@ -1249,23 +1312,28 @@ void DynaRecCPU::recSWR(uint32_t code) { const uint32_t alignedAddress = address & ~3; const uint32_t mask = SWR_MASK[address & 3]; const auto shift = SWR_SHIFT[address & 3]; + const uint32_t msanWriteMask = (0b1111 << (address & 3)) & 0b1111; gen.mov(arg1, alignedAddress); // Address in arg1 - call(read32Wrapper); + gen.mov(arg2, 0x0); // Mask in arg2 + call(read32MaskedWrapper); gen.andImm(eax, eax, mask); // Mask read value gen.or_(eax, m_gprs[_Rt_].val << shift); // Shift $rt and or with read value gen.mov(arg1, alignedAddress); // Address in arg2 again gen.mov(arg2, eax); // Address to write to in arg2 - call(write32Wrapper); + gen.mov(arg3, msanWriteMask); // Mask in arg3 + call(write32MaskedWrapper); } else if (m_gprs[_Rs_].isConst()) { // Only address is constant const uint32_t address = m_gprs[_Rs_].val + _Imm_; const uint32_t alignedAddress = address & ~3; const uint32_t mask = SWR_MASK[address & 3]; const auto shift = SWR_SHIFT[address & 3]; + const uint32_t msanWriteMask = (0b1111 << (address & 3)) & 0b1111; gen.mov(arg1, alignedAddress); // Address in arg1 - call(read32Wrapper); + gen.mov(arg2, 0x0); // Mask in arg2 + call(read32MaskedWrapper); gen.andImm(eax, eax, mask); // Mask read value gen.mov(arg1, alignedAddress); // Aligned address in arg1 again @@ -1273,12 +1341,14 @@ void DynaRecCPU::recSWR(uint32_t code) { gen.mov(arg2, m_gprs[_Rt_].allocatedReg); // Move rt to arg2 gen.shlImm(arg2, shift); // Shift rt value gen.or_(arg2, eax); // Or with read value - call(write32Wrapper); // Write back + gen.mov(arg3, msanWriteMask); // Mask in arg3 + call(write32MaskedWrapper); // Write back } else if (m_gprs[_Rt_].isConst()) { // Only previous rt value is constant allocateReg(_Rs_); // Allocate address reg gen.moveAndAdd(arg1, m_gprs[_Rs_].allocatedReg, _Imm_); // Address in arg1 gen.and_(arg1, ~3); // Force align it - call(read32Wrapper); // Read from the aligned address, result in eax + gen.mov(arg2, 0x0); // Mask in arg2 + call(read32MaskedWrapper); // Read from the aligned address, result in eax // The call might have flushed $rs, so we need to allocate it again, and also allocate $rt allocateReg(_Rs_); @@ -1307,12 +1377,16 @@ void DynaRecCPU::recSWR(uint32_t code) { gen.mov(arg1, arg4); } - call(write32Wrapper); + gen.mov(arg3, 0b1111); + // gen.shl(arg3, dl); + // gen.and_(arg3, 0b1111); // Mask in arg3 + call(write32MaskedWrapper); } else { // Nothing is constant allocateReg(_Rs_); // Allocate address reg gen.moveAndAdd(arg1, m_gprs[_Rs_].allocatedReg, _Imm_); // Address in arg1 gen.and_(arg1, ~3); // Force align it - call(read32Wrapper); // Read from the aligned address, result in eax + gen.mov(arg2, 0x0); // Mask in arg2 + call(read32MaskedWrapper); // Read from the aligned address, result in eax // The call might have flushed $rs, so we need to allocate it again, and also allocate $rt alloc_rt_rs(code); @@ -1341,7 +1415,10 @@ void DynaRecCPU::recSWR(uint32_t code) { gen.mov(arg1, arg4); } - call(write32Wrapper); + gen.mov(arg3, 0b1111); + // gen.shl(arg3, cl); + // gen.and_(arg3, 0b1111); // Mask in arg3 + call(write32MaskedWrapper); } } diff --git a/src/core/DynaRec_x64/recompiler.h b/src/core/DynaRec_x64/recompiler.h index 8e77501ff..8f4ae12ac 100644 --- a/src/core/DynaRec_x64/recompiler.h +++ b/src/core/DynaRec_x64/recompiler.h @@ -84,7 +84,13 @@ #define _ImmLU_ _fImmLU_(code) static uint32_t read32Wrapper(uint32_t address) { return PCSX::g_emulator->m_mem->read32(address); } +static uint32_t read32MaskedWrapper(uint32_t address, const uint32_t msanSubBitmask) { + return PCSX::g_emulator->m_mem->read32(address, PCSX::Memory::ReadType::Data, msanSubBitmask); +} static void write32Wrapper(uint32_t address, uint32_t value) { PCSX::g_emulator->m_mem->write32(address, value); } +static void write32MaskedWrapper(uint32_t address, uint32_t value, const uint32_t msanSubBitmask) { + PCSX::g_emulator->m_mem->write32(address, value, msanSubBitmask); +} static void SPU_writeRegisterWrapper(uint32_t addr, uint16_t value) { PCSX::g_emulator->m_spu->writeRegister(addr, value); } diff --git a/src/core/DynaRec_x64/symbols.cc b/src/core/DynaRec_x64/symbols.cc index 3897ff67a..5398229c3 100644 --- a/src/core/DynaRec_x64/symbols.cc +++ b/src/core/DynaRec_x64/symbols.cc @@ -26,9 +26,10 @@ #include "fmt/format.h" #define REGISTER_VARIABLE(variable, name, size) \ - m_symbols += fmt::format("{} {} {}\n", (void*)&(variable), (name), (size)) + m_symbols += fmt::format("{} {} {}\n", reinterpret_cast(&(variable)), (name), (size)) -#define REGISTER_FUNCTION(function, name) m_symbols += fmt::format("{} {}\n", (void*)&(function), (name)); +#define REGISTER_FUNCTION(function, name) \ + m_symbols += fmt::format("{} {}\n", reinterpret_cast(&(function)), (name)); #define REGISTER_CLASS_FUNCTION(function, name) \ { \ @@ -93,7 +94,9 @@ void DynaRecCPU::makeSymbols() { } REGISTER_FUNCTION(read32Wrapper, "read32_wrapper"); + REGISTER_FUNCTION(read32MaskedWrapper, "read32_masked_wrapper"); REGISTER_FUNCTION(write32Wrapper, "write32_wrapper"); + REGISTER_FUNCTION(write32MaskedWrapper, "write32_masked_wrapper"); REGISTER_CLASS_FUNCTION(PCSX::Memory::read8, "read8"); REGISTER_CLASS_FUNCTION(PCSX::Memory::read16, "read16"); diff --git a/src/core/gpu.cc b/src/core/gpu.cc index cf9067c24..2f6b35c12 100644 --- a/src/core/gpu.cc +++ b/src/core/gpu.cc @@ -327,6 +327,11 @@ uint32_t PCSX::GPU::gpuDmaChainSize(uint32_t addr) { if (usingMsan && PCSX::Memory::inMsanRange(addr)) { addr &= 0xfffffffc; switch (g_emulator->m_mem->msanGetStatus<4>(addr)) { + case PCSX::MsanStatus::PARTIALLY_INITIALIZED: + g_system->log(LogClass::GPU, + _("GPU DMA went into uninitialized bytes in usable, partially initialized msan memory: %8.8lx\n"), + addr); + break; case PCSX::MsanStatus::UNINITIALIZED: g_system->log(LogClass::GPU, _("GPU DMA went into usable but uninitialized msan memory: %8.8lx\n"), addr); @@ -596,6 +601,11 @@ void PCSX::GPU::chainedDMAWrite(const uint32_t *memory, uint32_t hwAddr) { addr &= 0xfffffffc; const uint32_t *headerPtr = (uint32_t *)(g_emulator->m_mem->m_msanRAM + (addr - PCSX::Memory::c_msanStart)); switch (g_emulator->m_mem->msanGetStatus<4>(addr)) { + case PCSX::MsanStatus::PARTIALLY_INITIALIZED: + g_system->log(LogClass::GPU, + _("GPU DMA went into uninitialized bytes in usable, partially initialized msan memory: %8.8lx\n"), + addr); + break; case PCSX::MsanStatus::UNINITIALIZED: g_system->log(LogClass::GPU, _("GPU DMA went into usable but uninitialized msan memory: %8.8lx\n"), addr); diff --git a/src/core/psxhw.h b/src/core/psxhw.h index 167c5d00e..27f000e8c 100644 --- a/src/core/psxhw.h +++ b/src/core/psxhw.h @@ -78,6 +78,11 @@ class HW { if (usingMsan && PCSX::Memory::inMsanRange(madr)) { madr &= 0xfffffffc; switch (g_emulator->m_mem->msanGetStatus<4>(madr)) { + case MsanStatus::PARTIALLY_INITIALIZED: + g_system->log(LogClass::GPU, + _("GPU DMA went into uninitialized bytes in usable, partially initialized msan memory: %8.8lx\n"), + madr); + break; case PCSX::MsanStatus::UNINITIALIZED: g_system->log(LogClass::GPU, _("GPU DMA went into usable but uninitialized msan memory: %8.8lx\n"), diff --git a/src/core/psxinterpreter.cc b/src/core/psxinterpreter.cc index d7a0654c3..1fb33078d 100644 --- a/src/core/psxinterpreter.cc +++ b/src/core/psxinterpreter.cc @@ -25,6 +25,7 @@ #include "core/pgxp_debug.h" #include "core/pgxp_gte.h" #include "core/psxemulator.h" +#include "core/psxmem.h" #include "core/r3000a.h" #include "tracy/Tracy.hpp" @@ -935,7 +936,12 @@ void InterpretedCPU::psxLW(uint32_t code) { void InterpretedCPU::psxLWL(uint32_t code) { uint32_t addr = _oB_; uint32_t shift = addr & 3; - uint32_t mem = PCSX::g_emulator->m_mem->read32(addr & ~3); + const uint32_t msanReadMask = 0b1111 >> (3 - shift); + uint32_t mem = PCSX::g_emulator->m_mem->read32( + addr & ~3, + PCSX::Memory::ReadType::Data, + msanReadMask + ); // load delay = 1 latency if (!_Rt_) return; @@ -953,7 +959,12 @@ void InterpretedCPU::psxLWL(uint32_t code) { void InterpretedCPU::psxLWR(uint32_t code) { uint32_t addr = _oB_; uint32_t shift = addr & 3; - uint32_t mem = PCSX::g_emulator->m_mem->read32(addr & ~3); + const uint32_t msanReadMask = (0b1111 << shift) & 0b1111; + uint32_t mem = PCSX::g_emulator->m_mem->read32( + addr & ~3, + PCSX::Memory::ReadType::Data, + msanReadMask + ); // load delay = 1 latency if (!_Rt_) return; @@ -998,15 +1009,18 @@ void InterpretedCPU::psxSWL(uint32_t code) { uint32_t addr = _oB_; uint32_t shift = addr & 3; addr ^= shift; - uint32_t mem; - // special handling to avoid msan interpreting this as a read - if (PCSX::g_emulator->m_mem->msanInitialized() && PCSX::g_emulator->m_mem->inMsanRange(addr)) { - mem = *(uint32_t *)&PCSX::g_emulator->m_mem->m_msanRAM[addr - PCSX::Memory::c_msanStart]; - } else { - mem = PCSX::g_emulator->m_mem->read32(addr); - } - - PCSX::g_emulator->m_mem->write32(addr, (_u32(_rRt_) >> SWL_SHIFT[shift]) | (mem & SWL_MASK[shift])); + // 0x0 mask avoids msan interpreting this as a read + uint32_t mem = PCSX::g_emulator->m_mem->read32( + addr, + PCSX::Memory::ReadType::Data, + 0x0 + ); + const uint32_t msanWriteMask = 0b1111 >> (3 - shift); + PCSX::g_emulator->m_mem->write32( + addr, + (_u32(_rRt_) >> SWL_SHIFT[shift]) | (mem & SWL_MASK[shift]), + msanWriteMask + ); /* Mem = 1234. Reg = abcd 0 123a (reg >> 24) | (mem & 0xffffff00) @@ -1020,15 +1034,18 @@ void InterpretedCPU::psxSWR(uint32_t code) { uint32_t addr = _oB_; uint32_t shift = addr & 3; addr ^= shift; - uint32_t mem; - // special handling to avoid msan interpreting this as a read - if (PCSX::g_emulator->m_mem->msanInitialized() && PCSX::g_emulator->m_mem->inMsanRange(addr)) { - mem = *(uint32_t *)&PCSX::g_emulator->m_mem->m_msanRAM[addr - PCSX::Memory::c_msanStart]; - } else { - mem = PCSX::g_emulator->m_mem->read32(addr); - } - - PCSX::g_emulator->m_mem->write32(addr, (_u32(_rRt_) << SWR_SHIFT[shift]) | (mem & SWR_MASK[shift])); + // 0x0 mask avoids msan interpreting this as a read + uint32_t mem = PCSX::g_emulator->m_mem->read32( + addr, + PCSX::Memory::ReadType::Data, + 0x0 + ); + const uint32_t msanWriteMask = (0b1111 << shift) & 0b1111; + PCSX::g_emulator->m_mem->write32( + addr, + (_u32(_rRt_) << SWR_SHIFT[shift]) | (mem & SWR_MASK[shift]), + msanWriteMask + ); /* Mem = 1234. Reg = abcd diff --git a/src/core/psxmem.cc b/src/core/psxmem.cc index a40a621a9..3bbe10111 100644 --- a/src/core/psxmem.cc +++ b/src/core/psxmem.cc @@ -27,6 +27,7 @@ #include "core/pio-cart.h" #include "core/psxhw.h" #include "core/r3000a.h" +#include "core/system.h" #include "mips/common/util/encoder.hh" #include "support/file.h" #include "supportpsx/binloader.h" @@ -297,6 +298,11 @@ uint8_t PCSX::Memory::read8(uint32_t address) { if (pointer != nullptr) { if (msanInitialized() && inMsanRange(address)) [[unlikely]] { switch (msanGetStatus<1>(address)) { + case MsanStatus::PARTIALLY_INITIALIZED: + g_system->log(LogClass::CPU, + _("8-bit read from uninitialized bytes in usable, partially initialized msan memory: %8.8lx\n"), + address); + break; case MsanStatus::UNINITIALIZED: g_system->log(LogClass::CPU, _("8-bit read from usable but uninitialized msan memory: %8.8lx\n"), address); @@ -348,6 +354,11 @@ uint16_t PCSX::Memory::read16(uint32_t address) { if (pointer != nullptr) { if (msanInitialized() && inMsanRange(address)) { switch (msanGetStatus<2>(address)) { + case MsanStatus::PARTIALLY_INITIALIZED: + g_system->log(LogClass::CPU, + _("16-bit read from uninitialized bytes in usable, partially initialized msan memory: %8.8lx\n"), + address); + break; case MsanStatus::UNINITIALIZED: g_system->log(LogClass::CPU, _("16-bit read from usable but uninitialized msan memory: %8.8lx\n"), address); @@ -387,7 +398,7 @@ uint16_t PCSX::Memory::read16(uint32_t address) { return 0xffff; } -uint32_t PCSX::Memory::read32(uint32_t address, ReadType readType) { +uint32_t PCSX::Memory::read32(uint32_t address, ReadType readType, const uint32_t msanSubBitmask) { if (readType == ReadType::Data) g_emulator->m_cpu->m_regs.cycle += 1; const uint32_t page = address >> 16; const auto pointer = (uint8_t *)m_readLUT[page]; @@ -395,7 +406,12 @@ uint32_t PCSX::Memory::read32(uint32_t address, ReadType readType) { if (pointer != nullptr) { if (msanInitialized() && inMsanRange(address)) { - switch (msanGetStatus<4>(address)) { + switch (msanGetStatus<4>(address, msanSubBitmask)) { + case MsanStatus::PARTIALLY_INITIALIZED: + g_system->log(LogClass::CPU, + _("32-bit read from uninitialized bytes in usable, partially initialized msan memory: %8.8lx\n"), + address); + break; case MsanStatus::UNINITIALIZED: g_system->log(LogClass::CPU, _("32-bit read from usable but uninitialized msan memory: %8.8lx\n"), address); @@ -586,7 +602,7 @@ void PCSX::Memory::write16(uint32_t address, uint32_t value) { } } -void PCSX::Memory::write32(uint32_t address, uint32_t value) { +void PCSX::Memory::write32(uint32_t address, uint32_t value, const uint32_t msanSubBitmask) { g_emulator->m_cpu->m_regs.cycle += 1; const uint32_t page = address >> 16; const auto pointer = (uint8_t *)m_writeLUT[page]; @@ -594,7 +610,7 @@ void PCSX::Memory::write32(uint32_t address, uint32_t value) { if (pointer != nullptr) { if (msanInitialized() && inMsanRange(address)) { - if (msanValidateWrite<4>(address)) { + if (msanValidateWrite<4>(address, msanSubBitmask)) { *(uint32_t *)&m_msanRAM[address - c_msanStart] = SWAP_LEu32(value); } else { g_system->log(LogClass::CPU, _("32-bit write to unusable msan memory: %8.8lx\n"), address); @@ -884,6 +900,7 @@ uint32_t PCSX::Memory::msanAlloc(uint32_t size) { // Check if we still have enough memory. if (m_msanPtr + actualSize > c_msanSize) { + g_system->printf(_("m_msanPtr: 0x%x actualSize: %zu, c_msanSize: %zu\n"), m_msanPtr, actualSize, c_msanSize); g_system->printf(_("Out of memory in MsanAlloc\n")); g_system->pause(); return 0; diff --git a/src/core/psxmem.h b/src/core/psxmem.h index 082567b47..708fe0455 100644 --- a/src/core/psxmem.h +++ b/src/core/psxmem.h @@ -48,9 +48,11 @@ namespace PCSX { enum class MsanStatus { - UNUSABLE, // memory that hasn't been allocated or has been freed - UNINITIALIZED, // allocated memory that has never been written to, has undefined contents - OK // free to use + UNUSABLE, // memory that hasn't been allocated or has been freed + UNINITIALIZED, // allocated memory that has never been written to, has undefined contents + PARTIALLY_INITIALIZED, // allocated memory that has had some subset of bytes being read initialised + // but not all of them + OK // free to use }; class Memory { @@ -66,10 +68,10 @@ class Memory { uint8_t read8(uint32_t address); uint16_t read16(uint32_t address); - uint32_t read32(uint32_t address, ReadType = ReadType::Data); + uint32_t read32(uint32_t address, ReadType = ReadType::Data, const uint32_t msanSubBitmask = UINT32_MAX); void write8(uint32_t address, uint32_t value); void write16(uint32_t address, uint32_t value); - void write32(uint32_t address, uint32_t value); + void write32(uint32_t address, uint32_t value, const uint32_t msanSubBitmask = UINT32_MAX); const void *pointerRead(uint32_t address); const void *pointerWrite(uint32_t address, int size); @@ -92,33 +94,35 @@ class Memory { uint32_t msanGetChainPtr(uint32_t addr) const; template - MsanStatus msanGetStatus(uint32_t addr) const { + MsanStatus msanGetStatus(uint32_t addr, const uint32_t subBitmask = UINT32_MAX) const { uint32_t bitmapIndex = (addr - c_msanStart) / 8; - uint32_t bitmask = ((1 << length) - 1) << addr % 8; + uint32_t bitmask = (((1 << length) - 1) << addr % 8) & subBitmask; MsanStatus bestCase = MsanStatus::OK; if (uint32_t nextBitmask = bitmask >> 8) [[unlikely]] { - if ((m_msanInitializedBitmap[bitmapIndex + 1] & nextBitmask) != nextBitmask) { + const uint8_t nextInitEntry = m_msanInitializedBitmap[bitmapIndex + 1] & nextBitmask; + if (nextInitEntry != nextBitmask) { if ((m_msanUsableBitmap[bitmapIndex + 1] & nextBitmask) != nextBitmask) { return MsanStatus::UNUSABLE; } - bestCase = MsanStatus::UNINITIALIZED; + bestCase = nextInitEntry ? MsanStatus::PARTIALLY_INITIALIZED : MsanStatus::UNINITIALIZED; } bitmask &= 0xff; } - if ((m_msanInitializedBitmap[bitmapIndex] & bitmask) != bitmask) [[unlikely]] { + const uint8_t initEntry = m_msanInitializedBitmap[bitmapIndex] & bitmask; + if (initEntry != bitmask) [[unlikely]] { if ((m_msanUsableBitmap[bitmapIndex] & bitmask) != bitmask) { return MsanStatus::UNUSABLE; } - return MsanStatus::UNINITIALIZED; + return initEntry ? MsanStatus::PARTIALLY_INITIALIZED : MsanStatus::UNINITIALIZED; } return bestCase; } // if the write is valid, marks the address as initialized, otherwise returns false template - bool msanValidateWrite(uint32_t addr) { + bool msanValidateWrite(uint32_t addr, const uint32_t subBitmask = UINT32_MAX) { uint32_t bitmapIndex = (addr - c_msanStart) / 8; - uint32_t bitmask = ((1 << length) - 1) << addr % 8; + uint32_t bitmask = (((1 << length) - 1) << addr % 8) & subBitmask; if (uint32_t nextBitmask = bitmask >> 8) [[unlikely]] { if ((m_msanUsableBitmap[bitmapIndex + 1] & nextBitmask) != nextBitmask) { return false; diff --git a/src/main/main.cc b/src/main/main.cc index f52d833f1..0521a6c22 100644 --- a/src/main/main.cc +++ b/src/main/main.cc @@ -17,10 +17,13 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * ***************************************************************************/ +#include "main/main.h" +#include #include #include #include #include +#include #include #include "core/arguments.h" @@ -30,6 +33,7 @@ #include "core/psxemulator.h" #include "core/r3000a.h" #include "core/sstate.h" +#include "core/system.h" #include "core/ui.h" #include "flags.h" #include "fmt/chrono.h" @@ -155,7 +159,7 @@ class SystemImpl final : public PCSX::System { void useLogfile(const PCSX::u8string &filename) { m_logfile.setFile(new PCSX::UvFile(filename, PCSX::FileOps::TRUNCATE)); } - bool m_inStartup = true; + std::atomic_bool m_inStartup = true; }; struct Cleaner { @@ -484,3 +488,11 @@ runner.init({ return exitCode; } + +bool MainInvoker::isInStartup() { + if (PCSX::g_system == nullptr || PCSX::g_emulator == nullptr) { + return true; + } + SystemImpl* system = reinterpret_cast(PCSX::g_system); + return system->m_inStartup; +} diff --git a/src/main/main.h b/src/main/main.h index 8a4f662f3..03004c36d 100644 --- a/src/main/main.h +++ b/src/main/main.h @@ -50,6 +50,7 @@ class MainInvoker { fprintf(stderr, "PCSX-Redux test finished with exit code %d\n", r); return r; } + bool isInStartup(); private: int m_count; diff --git a/src/mips/tests/Makefile b/src/mips/tests/Makefile index 6006a8841..b08b5a254 100644 --- a/src/mips/tests/Makefile +++ b/src/mips/tests/Makefile @@ -38,6 +38,8 @@ all: $(MAKE) -C libc all $(MAKE) -C memcpy all $(MAKE) -C memset all + $(MAKE) -C msan-invalid all + $(MAKE) -C msan-valid all $(MAKE) -C pcdrv all $(MAKE) -C psyqo all $(MAKE) -C regwrites all @@ -83,6 +85,8 @@ clean: $(MAKE) -C libc clean $(MAKE) -C memcpy clean $(MAKE) -C memset clean + $(MAKE) -C msan-invalid clean + $(MAKE) -C msan-valid clean $(MAKE) -C pcdrv clean $(MAKE) -C psyqo clean $(MAKE) -C regwrites clean diff --git a/src/mips/tests/msan-invalid/Makefile b/src/mips/tests/msan-invalid/Makefile new file mode 100644 index 000000000..91f5fe748 --- /dev/null +++ b/src/mips/tests/msan-invalid/Makefile @@ -0,0 +1,49 @@ +TARGET = msan-invalid +USE_FUNCTION_SECTIONS = false +TYPE = ps-exe + +SRCS = \ +../uC-sdk-glue/BoardConsole.c \ +../uC-sdk-glue/BoardInit.c \ +../uC-sdk-glue/init.c \ +\ +../../../../third_party/uC-sdk/libc/src/cxx-glue.c \ +../../../../third_party/uC-sdk/libc/src/errno.c \ +../../../../third_party/uC-sdk/libc/src/initfini.c \ +../../../../third_party/uC-sdk/libc/src/malloc.c \ +../../../../third_party/uC-sdk/libc/src/qsort.c \ +../../../../third_party/uC-sdk/libc/src/rand.c \ +../../../../third_party/uC-sdk/libc/src/reent.c \ +../../../../third_party/uC-sdk/libc/src/stdio.c \ +../../../../third_party/uC-sdk/libc/src/string.c \ +../../../../third_party/uC-sdk/libc/src/strto.c \ +../../../../third_party/uC-sdk/libc/src/unistd.c \ +../../../../third_party/uC-sdk/libc/src/xprintf.c \ +../../../../third_party/uC-sdk/libc/src/xscanf.c \ +../../../../third_party/uC-sdk/libc/src/yscanf.c \ +../../../../third_party/uC-sdk/os/src/devfs.c \ +../../../../third_party/uC-sdk/os/src/filesystem.c \ +../../../../third_party/uC-sdk/os/src/fio.c \ +../../../../third_party/uC-sdk/os/src/hash-djb2.c \ +../../../../third_party/uC-sdk/os/src/init.c \ +../../../../third_party/uC-sdk/os/src/osdebug.c \ +../../../../third_party/uC-sdk/os/src/romfs.c \ +../../../../third_party/uC-sdk/os/src/sbrk.c \ + + +CPPFLAGS = -DNOFLOATINGPOINT +CPPFLAGS += -I. +CPPFLAGS += -I../../../../third_party/uC-sdk/libc/include +CPPFLAGS += -I../../../../third_party/uC-sdk/os/include +CPPFLAGS += -I../../../../third_party/libcester/include +CPPFLAGS += -I../../openbios/uC-sdk-glue + +ifeq ($(PCSX_TESTS),true) +CPPFLAGS += -DPCSX_TESTS=1 +endif + +SRCS += \ +../../common/crt0/uC-sdk-crt0.s \ +msan_invalid.c \ + +include ../../common.mk diff --git a/src/mips/tests/msan-invalid/msan_invalid.c b/src/mips/tests/msan-invalid/msan_invalid.c new file mode 100644 index 000000000..3dd5e6666 --- /dev/null +++ b/src/mips/tests/msan-invalid/msan_invalid.c @@ -0,0 +1,135 @@ +/* + +MIT License + +Copyright (c) 2020 PCSX-Redux authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ +#include +#include "common/hardware/pcsxhw.h" + +#if PCSX_TESTS +#define CESTER_MAYBE_TEST CESTER_SKIP_TEST +#else +#define CESTER_MAYBE_TEST CESTER_TEST +#endif + +#undef unix +#define CESTER_NO_SIGNAL +#define CESTER_NO_TIME +#define EXIT_SUCCESS 0 +#define EXIT_FAILURE 1 +#include "exotic/cester.h" + +// clang-format off + +CESTER_BEFORE_EACH(test_instance, test_name, index, + test_instance->arg = pcsx_msanAlloc(sizeof(uint32_t)); +) + +CESTER_AFTER_EACH(test_instance, test_name, index, + pcsx_msanFree(test_instance->arg); +) + +CESTER_BEFORE_ALL(test_instance, + pcsx_initMsan(); +) + +CESTER_AFTER_ALL(test_instance, + pcsx_resetMsan(); +) + +// clang-format on + +#define INVALID_SWX_LWX(name, sw_suffix, lw_suffix, store_off, load_off) \ + CESTER_MAYBE_TEST(name, test_instance, \ + register uint32_t* mem_32_bit = (uint32_t*) test_instance->arg; \ + const register uint32_t value = 0x11223344; \ + __asm__ __volatile__( \ + "sw" #sw_suffix " %0, " #store_off "(%1);" \ + : \ + : "r"(value), "r"(mem_32_bit) \ + ); \ + register volatile uint32_t result = 0xAABBCCDD; \ + __asm__ __volatile__( \ + "lw" #lw_suffix " %0, " #load_off "(%1);" \ + : "+r"(result) \ + : "r"(mem_32_bit) \ + ); \ + /* Should leave our existing registered value untouched */ \ + cester_assert_equal(result, 0xAABBCCDD); \ + /* BUG: For some reason the pointer value in test_instance->arg */ \ + /* is mutated from the 0x20000000+ range to the 0x80000000+ address range. */ \ + /* This is to restore the correct address.*/ \ + test_instance->arg = mem_32_bit; \ + ) + +// SWL => LWL +INVALID_SWX_LWX(msan_8bit_swl_16bit_lwl, l, l, 0, 1) +INVALID_SWX_LWX(msan_8bit_swl_24bit_lwl, l, l, 0, 2) +INVALID_SWX_LWX(msan_8bit_swl_32bit_lwl, l, l, 0, 3) + +INVALID_SWX_LWX(msan_16bit_swl_24bit_lwl, l, l, 1, 2) +INVALID_SWX_LWX(msan_16bit_swl_32bit_lwl, l, l, 1, 3) + +INVALID_SWX_LWX(msan_24bit_swl_32bit_lwl, l, l, 2, 3) + +// SWL => LWR +INVALID_SWX_LWX(msan_8bit_swl_32bit_lwr, l, r, 0, 0) +INVALID_SWX_LWX(msan_8bit_swl_24bit_lwr, l, r, 0, 1) +INVALID_SWX_LWX(msan_8bit_swl_16bit_lwr, l, r, 0, 2) +INVALID_SWX_LWX(msan_8bit_swl_8bit_lwr, l, r, 0, 3) + +INVALID_SWX_LWX(msan_16bit_swl_32bit_lwr, l, r, 1, 0) +INVALID_SWX_LWX(msan_16bit_swl_24bit_lwr, l, r, 1, 1) +INVALID_SWX_LWX(msan_16bit_swl_16bit_lwr, l, r, 1, 2) +INVALID_SWX_LWX(msan_16bit_swl_8bit_lwr, l, r, 1, 3) + +INVALID_SWX_LWX(msan_24bit_swl_32bit_lwr, l, r, 2, 0) +INVALID_SWX_LWX(msan_24bit_swl_24bit_lwr, l, r, 2, 1) +INVALID_SWX_LWX(msan_24bit_swl_16bit_lwr, l, r, 2, 2) +INVALID_SWX_LWX(msan_24bit_swl_8bit_lwr, l, r, 2, 3) + +// SWR => LWR +INVALID_SWX_LWX(msan_8bit_swr_32bit_lwr, r, r, 3, 0) +INVALID_SWX_LWX(msan_8bit_swr_24bit_lwr, r, r, 3, 1) +INVALID_SWX_LWX(msan_8bit_swr_16bit_lwr, r, r, 3, 2) + +INVALID_SWX_LWX(msan_16bit_swr_32bit_lwr, r, r, 2, 0) +INVALID_SWX_LWX(msan_16bit_swr_24bit_lwr, r, r, 2, 1) + +INVALID_SWX_LWX(msan_24bit_swr_32bit_lwr, r, r, 3, 0) + +// SWR => LWL +INVALID_SWX_LWX(msan_8bit_swr_32bit_lwl, r, l, 3, 0) +INVALID_SWX_LWX(msan_8bit_swr_24bit_lwl, r, l, 3, 1) +INVALID_SWX_LWX(msan_8bit_swr_16bit_lwl, r, l, 3, 2) +INVALID_SWX_LWX(msan_8bit_swr_8bit_lwl, r, l, 3, 3) + +INVALID_SWX_LWX(msan_16bit_swr_32bit_lwl, r, l, 2, 0) +INVALID_SWX_LWX(msan_16bit_swr_24bit_lwl, r, l, 2, 1) +INVALID_SWX_LWX(msan_16bit_swr_16bit_lwl, r, l, 2, 2) +INVALID_SWX_LWX(msan_16bit_swr_8bit_lwl, r, l, 2, 3) + +INVALID_SWX_LWX(msan_24bit_swr_32bit_lwl, r, l, 1, 0) +INVALID_SWX_LWX(msan_24bit_swr_24bit_lwl, r, l, 1, 1) +INVALID_SWX_LWX(msan_24bit_swr_16bit_lwl, r, l, 1, 2) +INVALID_SWX_LWX(msan_24bit_swr_8bit_lwl, r, l, 1, 3) diff --git a/src/mips/tests/msan-valid/Makefile b/src/mips/tests/msan-valid/Makefile new file mode 100644 index 000000000..d10c8b85c --- /dev/null +++ b/src/mips/tests/msan-valid/Makefile @@ -0,0 +1,49 @@ +TARGET = msan-valid +USE_FUNCTION_SECTIONS = false +TYPE = ps-exe + +SRCS = \ +../uC-sdk-glue/BoardConsole.c \ +../uC-sdk-glue/BoardInit.c \ +../uC-sdk-glue/init.c \ +\ +../../../../third_party/uC-sdk/libc/src/cxx-glue.c \ +../../../../third_party/uC-sdk/libc/src/errno.c \ +../../../../third_party/uC-sdk/libc/src/initfini.c \ +../../../../third_party/uC-sdk/libc/src/malloc.c \ +../../../../third_party/uC-sdk/libc/src/qsort.c \ +../../../../third_party/uC-sdk/libc/src/rand.c \ +../../../../third_party/uC-sdk/libc/src/reent.c \ +../../../../third_party/uC-sdk/libc/src/stdio.c \ +../../../../third_party/uC-sdk/libc/src/string.c \ +../../../../third_party/uC-sdk/libc/src/strto.c \ +../../../../third_party/uC-sdk/libc/src/unistd.c \ +../../../../third_party/uC-sdk/libc/src/xprintf.c \ +../../../../third_party/uC-sdk/libc/src/xscanf.c \ +../../../../third_party/uC-sdk/libc/src/yscanf.c \ +../../../../third_party/uC-sdk/os/src/devfs.c \ +../../../../third_party/uC-sdk/os/src/filesystem.c \ +../../../../third_party/uC-sdk/os/src/fio.c \ +../../../../third_party/uC-sdk/os/src/hash-djb2.c \ +../../../../third_party/uC-sdk/os/src/init.c \ +../../../../third_party/uC-sdk/os/src/osdebug.c \ +../../../../third_party/uC-sdk/os/src/romfs.c \ +../../../../third_party/uC-sdk/os/src/sbrk.c \ + + +CPPFLAGS = -DNOFLOATINGPOINT +CPPFLAGS += -I. +CPPFLAGS += -I../../../../third_party/uC-sdk/libc/include +CPPFLAGS += -I../../../../third_party/uC-sdk/os/include +CPPFLAGS += -I../../../../third_party/libcester/include +CPPFLAGS += -I../../openbios/uC-sdk-glue + +ifeq ($(PCSX_TESTS),true) +CPPFLAGS += -DPCSX_TESTS=1 +endif + +SRCS += \ +../../common/crt0/uC-sdk-crt0.s \ +msan_valid.c \ + +include ../../common.mk diff --git a/src/mips/tests/msan-valid/msan_valid.c b/src/mips/tests/msan-valid/msan_valid.c new file mode 100644 index 000000000..59618b2b7 --- /dev/null +++ b/src/mips/tests/msan-valid/msan_valid.c @@ -0,0 +1,205 @@ +/* + +MIT License + +Copyright (c) 2020 PCSX-Redux authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ +#include +#include "common/hardware/pcsxhw.h" + +#if PCSX_TESTS +#define CESTER_MAYBE_TEST CESTER_SKIP_TEST +#else +#define CESTER_MAYBE_TEST CESTER_TEST +#endif + +#undef unix +#define CESTER_NO_SIGNAL +#define CESTER_NO_TIME +#define EXIT_SUCCESS 0 +#define EXIT_FAILURE 1 +#include "exotic/cester.h" + +// clang-format off + +CESTER_BEFORE_EACH(test_instance, testname, testindex, + test_instance->arg = pcsx_msanAlloc(sizeof(uint32_t)); +) + +CESTER_AFTER_EACH(test_instance, testname, testindex, + pcsx_msanFree(test_instance->arg); +) + +CESTER_BEFORE_ALL(test_instance, + pcsx_initMsan(); +) + +CESTER_AFTER_ALL(test_instance, + pcsx_resetMsan(); +) + +// clang-format on + +CESTER_MAYBE_TEST(msan_32bit_sw_lw, test_instance, + register uint32_t* mem_32_bit = (uint32_t*) test_instance->arg; + register uint32_t value = 0x11223344; + register volatile uint32_t result = 0; + __asm__ __volatile__( + "sw %1, 0(%2);" + "lw %0, 0(%2);" + : "=r"(result) + : "r"(value), "r"(mem_32_bit) + ); + cester_assert_equal(result, value); +) + +#define LWX(load_suffix, offset, expected_result) \ + result = 0xAABBCCDD; \ + __asm__ __volatile__( \ + "lw" #load_suffix " %0, " #offset "(%1)" \ + : "+r"(result) \ + : "r"(mem_32_bit) \ + ); \ + cester_assert_equal(result, expected_result) + +// SWL -> LWL + +CESTER_MAYBE_TEST(msan_8bit_swl_8bit_lwl, test_instance, + register uint32_t* mem_32_bit = (uint32_t*) test_instance->arg; + register uint32_t value = 0x11223344; + // Store upper byte 0x11 + __asm__ __volatile__( + "swl %0, 0(%1)" + : + : "r"(value), "r"(mem_32_bit) + ); + register volatile uint32_t result = 0xAABBCCDD; + LWX(l, 0, 0x11BBCCDD); +) + +CESTER_MAYBE_TEST(msan_16bit_swl_8_to_16bit_lwl, test_instance, + register uint32_t* mem_32_bit = (uint32_t*) test_instance->arg; + register uint32_t value = 0x11223344; + // Store upper 2 bytes 0x1122 + __asm__ __volatile__( + "swl %0, 1(%1)" + : + : "r"(value), "r"(mem_32_bit) + ); + register volatile uint32_t result = 0xAABBCCDD; + LWX(l, 0, 0x22BBCCDD); + LWX(l, 1, 0x1122CCDD); +) + +CESTER_MAYBE_TEST(msan_24bit_swl_8_to_24bit_lwl, test_instance, + register uint32_t* mem_32_bit = (uint32_t*) test_instance->arg; + register uint32_t value = 0x11223344; + // Store upper 3 bytes 0x112233 + __asm__ __volatile__( + "swl %0, 2(%1)" + : + : "r"(value), "r"(mem_32_bit) + ); + register volatile uint32_t result = 0xAABBCCDD; + LWX(l, 0, 0x33BBCCDD); + LWX(l, 1, 0x2233CCDD); + LWX(l, 2, 0x112233DD); +) + +CESTER_MAYBE_TEST(msan_32bit_swl_8_to_32bit_lwl, test_instance, + register uint32_t* mem_32_bit = (uint32_t*) test_instance->arg; + register uint32_t value = 0x11223344; + // Store all 4 bytes 0x11223344 + __asm__ __volatile__( + "swl %0, 3(%1)" + : + : "r"(value), "r"(mem_32_bit) + ); + register volatile uint32_t result = 0xAABBCCDD; + LWX(l, 0, 0x44BBCCDD); + LWX(l, 1, 0x3344CCDD); + LWX(l, 2, 0x223344DD); + LWX(l, 3, 0x11223344); + LWX(, 0, 0x11223344); +) + +// SWR -> LWR + +CESTER_MAYBE_TEST(msan_8bit_swr_8bit_lwr, test_instance, + register uint32_t* mem_32_bit = (uint32_t*) test_instance->arg; + register uint32_t value = 0x11223344; + // Store lower byte 0x44 + __asm__ __volatile__( + "swr %0, 3(%1)" + : + : "r"(value), "r"(mem_32_bit) + ); + register volatile uint32_t result = 0xAABBCCDD; + LWX(r, 3, 0xAABBCC44); +) + +CESTER_MAYBE_TEST(msan_16bit_swr_8_to_16_bit_lwr, test_instance, + register uint32_t* mem_32_bit = (uint32_t*) test_instance->arg; + register uint32_t value = 0x11223344; + // Store lower 2 bytes 0x3344 + __asm__ __volatile__( + "swr %0, 2(%1)" + : + : "r"(value), "r"(mem_32_bit) + ); + register volatile uint32_t result = 0xAABBCCDD; + LWX(r, 3, 0xAABBCC33); + LWX(r, 2, 0xAABB3344); +) + +CESTER_MAYBE_TEST(msan_24bit_swr_8_to_24_bit_lwr, test_instance, + register uint32_t* mem_32_bit = (uint32_t*) test_instance->arg; + register uint32_t value = 0x11223344; + // Store lower 3 bytes 0x223344 + __asm__ __volatile__( + "swr %0, 1(%1)" + : + : "r"(value), "r"(mem_32_bit) + ); + register volatile uint32_t result = 0xAABBCCDD; + LWX(r, 3, 0xAABBCC22); + LWX(r, 2, 0xAABB2233); + LWX(r, 1, 0xAA223344); +) + +CESTER_MAYBE_TEST(msan_32bit_swr_8_to_32_bit_lwr, test_instance, + register uint32_t* mem_32_bit = (uint32_t*) test_instance->arg; + register uint32_t value = 0x11223344; + // Store lower 3 bytes 0x11223344 + __asm__ __volatile__( + "swr %0, 0(%1)" + : + : "r"(value), "r"(mem_32_bit) + ); + register volatile uint32_t result = 0xAABBCCDD; + LWX(r, 3, 0xAABBCC11); + LWX(r, 2, 0xAABB1122); + LWX(r, 1, 0xAA112233); + LWX(r, 0, 0x11223344); + LWX(, 0, 0x11223344); +) + diff --git a/tests/pcsxrunner/msan.cc b/tests/pcsxrunner/msan.cc new file mode 100644 index 000000000..680a8f391 --- /dev/null +++ b/tests/pcsxrunner/msan.cc @@ -0,0 +1,270 @@ +/*************************************************************************** + * Copyright (C) 2021 PCSX-Redux authors * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * + ***************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include "fmt/printf.h" +#include "gtest/gtest.h" +#include "main/main.h" +#include "core/psxmem.h" +#include "core/system.h" +#include "core/psxemulator.h" +#include "support/eventbus.h" + +using namespace std::chrono_literals; + +inline static const bool isMsanLog(const PCSX::Events::LogMessage& event) { + return event.logClass == PCSX::LogClass::CPU + && (event.message.contains("-bit read") || event.message.contains("-bit write")); +} + +// ==== VALID ==== + +void execValidTest(const char* type) { + std::atomic_int exitCode(std::numeric_limits::min()); + MainInvoker invoker("-no-ui", "-run", "-bios", "src/mips/openbios/openbios.bin", "-testmode", type, + "-luacov", "-loadexe", "src/mips/tests/msan-valid/msan-valid.ps-exe"); + std::thread thread([&](){ + exitCode.store(invoker.invoke()); + }); + while (invoker.isInStartup()); + std::atomic_bool shouldFail = false; + std::atomic_bool msanTriggered = false; + PCSX::EventBus::Listener listener(PCSX::g_system->m_eventBus); + listener.listen([&](const PCSX::Events::LogMessage& event) { + if (isMsanLog(event)) { + msanTriggered = true; + shouldFail = true; + } + }); + std::chrono::milliseconds elapsed(0); + PCSX::g_system->resume(); + while (elapsed < 30s + && exitCode.load() == std::numeric_limits::min()) { + if (msanTriggered) { + // Allow the tests to continue, even after triggering an MSAN + // log. This means the test results are printed and we still + // show a failure. + std::chrono::milliseconds awaitPauseElapsed(0); + while (awaitPauseElapsed < 5s && PCSX::g_system->running()) { + std::cout << "Awaiting pause" << std::endl; + std::this_thread::sleep_for(10ms); + awaitPauseElapsed += 10ms; + } + if (awaitPauseElapsed >= 5s) { + PCSX::g_system->quit(3); + break; + } + PCSX::g_system->resume(); + msanTriggered = false; + } + std::this_thread::sleep_for(10ms); + elapsed += 10ms; + } + if (elapsed >= 30s || shouldFail) { + PCSX::g_system->quit(1); + } + if (thread.joinable()) { + thread.join(); + } + ASSERT_FALSE(shouldFail.load()) << "Unexpected MSAN log encountered"; + ASSERT_EQ(exitCode.load(), 0); +} + +TEST(MSAN, InterpreterValid) { + ASSERT_NO_FATAL_FAILURE(execValidTest("-interpreter")); +} + +TEST(MSAN, DynarecValid) { + ASSERT_NO_FATAL_FAILURE(execValidTest("-dynarec")); +} + +// ==== INVALID ==== + +inline static const uint8_t inspectMsanInitializedBitmap(const uint32_t address) { + const uint32_t bitmapIndex = (address - PCSX::g_emulator->m_mem->c_msanStart) / 8; + return PCSX::g_emulator->m_mem->m_msanInitializedBitmap[bitmapIndex]; +} + +static constexpr uint8_t SWX_EXPECTED_BITMASKS[36] = { + 0b0001, // swl 0 (8-bit) => lwl 1 (16-bit) + 0b0001, // swl 0 (8-bit) => lwl 2 (24-bit) + 0b0001, // swl 0 (8-bit) => lwl 3 (32-bit) + // + 0b0011, // swl 1 (16-bit) => lwl 2 (24-bit) + 0b0011, // swl 1 (16-bit) => lwl 3 (32-bit) + + 0b0111, // swl 2 (24-bit) => lwl 3 (32-bit) + + 0b0001, // swl (8-bit) => lwr (32-bit) + 0b0001, // swl (8-bit) => lwr (24-bit) + 0b0001, // swl (8-bit) => lwr (16-bit) + 0b0001, // swl (8-bit) => lwr (8-bit) + + 0b0011, // swl (16-bit) => lwr (32-bit) + 0b0011, // swl (16-bit) => lwr (24-bit) + 0b0011, // swl (16-bit) => lwr (16-bit) + 0b0011, // swl (16-bit) => lwr (8-bit) + + 0b0111, // swl (24-bit) => lwr (32-bit) + 0b0111, // swl (24-bit) => lwr (24-bit) + 0b0111, // swl (24-bit) => lwr (16-bit) + 0b0111, // swl (24-bit) => lwr (8-bit) + + 0b1000, // swr (8-bit) => lwr (32-bit) + 0b1000, // swr (8-bit) => lwr (24-bit) + 0b1000, // swr (8-bit) => lwr (16-bit) + + 0b1100, // swr (16-bit) => lwr (32-bit) + 0b1100, // swr (16-bit) => lwr (24-bit) + + 0b1110, // swr (24-bit) => lwr (32-bit) + + 0b1000, // swr (8-bit) => lwl (32-bit) + 0b1000, // swr (8-bit) => lwl (24-bit) + 0b1000, // swr (8-bit) => lwl (16-bit) + 0b1000, // swr (8-bit) => lwl (8-bit) + + 0b1100, // swr (16-bit) => lwl (32-bit) + 0b1100, // swr (16-bit) => lwl (24-bit) + 0b1100, // swr (16-bit) => lwl (16-bit) + 0b1100, // swr (16-bit) => lwl (8-bit) + + 0b1110, // swr (24-bit) => lwl (32-bit) + 0b1110, // swr (24-bit) => lwl (24-bit) + 0b1110, // swr (24-bit) => lwl (16-bit) + 0b1110, // swr (24-bit) => lwl (8-bit) +}; + +inline std::string rtrim(std::string s) { + s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) { + return !std::isspace(ch); + }).base(), s.end()); + return s; +} + +std::optional nextMsanTest(const std::string& msg, + std::atomic_uint* nextMsanCheckIndex) { + if (!PCSX::g_system->running()) { + return fmt::sprintf("[Test index: %d] Expected emulator to not have reached pause state yet", nextMsanCheckIndex->load()); + } + const size_t allocCount = PCSX::g_emulator->m_mem->m_msanAllocs.size(); + if (allocCount != 1) { + return fmt::sprintf("[Test index: %d] Expected 1 MSAN allocation, got %zu", nextMsanCheckIndex->load(), allocCount); + } + const uint32_t msanAllocAddr = PCSX::g_emulator->m_mem->m_msanAllocs.begin()->first + PCSX::Memory::c_msanStart; + const std::string expectedMsg = fmt::sprintf("32-bit read from uninitialized bytes in usable, partially initialized msan memory: %8.8lx", msanAllocAddr); + const std::string trimmedMsg = rtrim(msg); + if (trimmedMsg != expectedMsg) { + return fmt::sprintf("[Test index: %d] Invalid MSAN event logged, expected: \"%s\", got: \"%s\"", nextMsanCheckIndex->load(), expectedMsg, trimmedMsg); + } + const uint8_t expectedInitBitmap = SWX_EXPECTED_BITMASKS[nextMsanCheckIndex->load()]; + const uint8_t actualInitBitmap = inspectMsanInitializedBitmap(msanAllocAddr); + if (expectedInitBitmap != actualInitBitmap) { + return fmt::sprintf( + "[Test index: %d] Initialized bitmap for address 0x%8.8lx mismatch: 0b%s != 0b%s", + nextMsanCheckIndex->load(), + msanAllocAddr, + std::bitset<8>(expectedInitBitmap).to_string(), + std::bitset<8>(actualInitBitmap).to_string() + ); + } + nextMsanCheckIndex->fetch_add(1); + return std::nullopt; +} + +void execInvalidTest(const char* type) { + std::atomic_int exitCode(std::numeric_limits::min()); + MainInvoker invoker("-no-ui", "-run", "-bios", "src/mips/openbios/openbios.bin", "-testmode", type, + "-luacov", "-loadexe", "src/mips/tests/msan-invalid/msan-invalid.ps-exe"); + std::thread thread([&](){ + exitCode.store(invoker.invoke()); + }); + while (invoker.isInStartup()); + std::atomic_bool resumeSystem = false; + std::optional result = std::nullopt; + std::mutex resultMutex; + std::atomic_uint nextMsanCheckIndex = 0; + PCSX::EventBus::Listener listener(PCSX::g_system->m_eventBus); + listener.listen([&](const PCSX::Events::LogMessage& event) { + if (PCSX::g_system->quitting() || !isMsanLog(event)) { + return; + } + std::lock_guard guard(resultMutex); + result = nextMsanTest(event.message, &nextMsanCheckIndex); + resumeSystem = !result.has_value(); + }); + std::chrono::milliseconds elapsed(0); + PCSX::g_system->resume(); + while (elapsed < 30s + && nextMsanCheckIndex <= std::size(SWX_EXPECTED_BITMASKS) + && exitCode.load() == std::numeric_limits::min()) { + { + std::lock_guard guard(resultMutex); + if (result.has_value()) { + PCSX::g_system->quit(2); + break; + } + // When an MSAN log is published, it will be handled + // first before a pause is triggered. Thus we need to + // wait until the pause is triggered and then ensure the + // emulator continues. This must be differentiated from + // the case where we haven't handled an MSAN log. + if (resumeSystem) { + std::chrono::milliseconds awaitPauseElapsed(0); + while (awaitPauseElapsed < 5s && PCSX::g_system->running()) { + std::cout << "Awaiting pause" << std::endl; + std::this_thread::sleep_for(10ms); + awaitPauseElapsed += 10ms; + } + if (awaitPauseElapsed >= 5s) { + PCSX::g_system->quit(3); + break; + } + resumeSystem = false; + PCSX::g_system->resume(); + } + } + std::this_thread::sleep_for(10ms); + elapsed += 10ms; + } + if (elapsed >= 30s) { + PCSX::g_system->quit(1); + } + if (thread.joinable()) { + thread.join(); + } + if (result.has_value()) { + FAIL() << result.value(); + } + ASSERT_EQ(exitCode.load(), 0); +} + +TEST(MSAN, InterpreterInvalid) { + ASSERT_NO_FATAL_FAILURE(execInvalidTest("-interpreter")); +} + +TEST(MSAN, DynarecInvalid) { + ASSERT_NO_FATAL_FAILURE(execInvalidTest("-dynarec")); +}