Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SPU: Implement "double" SNR storage #12258

Merged
merged 2 commits into from Jun 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 19 additions & 0 deletions rpcs3/Emu/Cell/SPUDisAsm.cpp
Expand Up @@ -337,6 +337,25 @@ void SPUDisAsm::WRCH(spu_opcode_t op)
fmt::append(last_opcode, " #%s", upd == "empty" ? "IMMEDIATE" : upd);
return;
}
case SPU_WrOutIntrMbox:
{
const u32 code = value._u32[3] >> 24;

if (code == 128u)
{
last_opcode += " #sys_event_flag_set_bit";
}
else if (code == 192u)
{
last_opcode += " #sys_event_flag_set_bit_impatient";
}
else
{
fmt::append(last_opcode, " #%s", SignedHex(value._u32[3]));
}

return;
}
default:
{
fmt::append(last_opcode, " #%s", SignedHex(value._u32[3]));
Expand Down
24 changes: 11 additions & 13 deletions rpcs3/Emu/Cell/SPUThread.cpp
Expand Up @@ -1695,7 +1695,7 @@ void spu_thread::push_snr(u32 number, u32 value)

// Prepare some data
const u32 event_bit = SPU_EVENT_S1 >> (number & 1);
const u32 bitor_bit = (snr_config >> number) & 1;
const bool bitor_bit = !!((snr_config >> number) & 1);

// Redundant, g_use_rtm is checked inside tx_start now.
if (g_use_rtm)
Expand All @@ -1705,10 +1705,16 @@ void spu_thread::push_snr(u32 number, u32 value)

const bool ok = utils::tx_start([&]
{
channel_notify = (channel->data.raw() & spu_channel::bit_wait) != 0;
channel_notify = (channel->data.raw() == spu_channel::bit_wait);
thread_notify = (channel->data.raw() & spu_channel::bit_count) == 0;

if (bitor_bit)
if (channel_notify)
{
ensure(channel->jostling_value.raw() == spu_channel::bit_wait);
channel->jostling_value.raw() = value;
channel->data.raw() = 0;
}
else if (bitor_bit)
{
channel->data.raw() &= ~spu_channel::bit_wait;
channel->data.raw() |= spu_channel::bit_count | value;
Expand Down Expand Up @@ -1752,16 +1758,8 @@ void spu_thread::push_snr(u32 number, u32 value)
});

// Check corresponding SNR register settings
if (bitor_bit)
{
if (channel->push_or(value))
set_events(event_bit);
}
else
{
if (channel->push(value))
set_events(event_bit);
}
if (channel->push(value, bitor_bit))
set_events(event_bit);

ch_events.atomic_op([](ch_events_t& ev)
{
Expand Down
115 changes: 67 additions & 48 deletions rpcs3/Emu/Cell/SPUThread.h
Expand Up @@ -166,13 +166,13 @@ enum : u32
SPU_FAKE_BASE_ADDR = 0xE8000000,
};

struct spu_channel
struct alignas(16) spu_channel
{
// Low 32 bits contain value
atomic_t<u64> data;

// Pending value to be inserted when it is possible at pop()
atomic_t<u32> jostling_value;
// Pending value to be inserted when it is possible in pop() or pop_wait()
atomic_t<u64> jostling_value;

public:
static constexpr u32 off_wait = 32;
Expand All @@ -195,39 +195,49 @@ struct spu_channel
}).second;
}

// Push performing bitwise OR with previous value, may require notification
bool push_or(u32 value)
// Push unconditionally, may require notification
// Performing bitwise OR with previous value if specified, otherwise overwiting it
bool push(u32 value, bool to_or = false)
{
const u64 old = data.fetch_op([value](u64& data)
while (true)
{
data &= ~bit_wait;
data |= bit_count | value;
});
const auto [old, pushed_to_data] = data.fetch_op([&](u64& data)
{
if (data == bit_wait)
{
return false;
}

if (old & bit_wait)
{
data.notify_one();
}
if (to_or)
{
data |= bit_count | value;
}
else
{
data = bit_count | value;
}

return (old & bit_count) == 0;
}
return true;
});

bool push_and(u32 value)
{
return (data.fetch_and(~u64{value}) & value) != 0;
}
if (!pushed_to_data)
{
// Insert the pending value in special storage for waiting SPUs, leave no time in which the channel has data
if (!jostling_value.compare_and_swap_test(bit_wait, value))
{
// Other thread has inserted a value through jostling_value, retry
continue;
}

// Push unconditionally (overwriting previous value), may require notification
bool push(u32 value)
{
const u64 old = data.exchange(bit_count | value);
// Turn off waiting bit manually (must succeed because waiting bit can only be resetted by the thread pushed to jostling_value)
ensure(this->data.bit_test_reset(off_wait));
}

if (old & bit_wait)
{
data.notify_one();
}

return (old & bit_count) == 0;
// Return true if count has changed from 0 to 1, this condition is considered satisfied even if we pushed a value directly to the special storage for waiting SPUs
return !pushed_to_data || (old & bit_count) == 0;
}
}

// Returns true on success
Expand All @@ -250,10 +260,10 @@ struct spu_channel
bool try_read(u32& out) const
{
const u64 old = data.load();
out = static_cast<u32>(old);

if (old & bit_count) [[likely]]
{
out = static_cast<u32>(old);
return true;
}

Expand All @@ -272,7 +282,7 @@ struct spu_channel
if ((data & mask) == mask)
{
// Insert the pending value, leave no time in which the channel has no data
data = bit_count | jostling_value;
data = bit_count | static_cast<u32>(jostling_value);
return;
}

Expand All @@ -290,41 +300,50 @@ struct spu_channel
// Waiting for channel pop state availability, actually popping if specified
s64 pop_wait(cpu_thread& spu, bool pop = true)
{
while (true)
u64 old = data.fetch_op([&](u64& data)
{
const u64 old = data.fetch_op([&](u64& data)
if (data & bit_count) [[likely]]
{
if (data & bit_count) [[likely]]
if (pop)
{
if (pop)
{
data = 0;
return true;
}

return false;
data = 0;
return true;
}

data = bit_wait;
return true;
}).first;
return false;
}

if (old & bit_count)
data = bit_wait;
jostling_value.release(bit_wait);
return true;
}).first;

if (old & bit_count)
{
return static_cast<u32>(old);
}

while (true)
{
thread_ctrl::wait_on(data, bit_wait);
old = data;

if (!(old & bit_wait))
{
return static_cast<u32>(old);
return static_cast<u32>(jostling_value);
}

if (spu.is_stopped())
{
if (u64 old2 = data.exchange(0); old2 & bit_count)
// Abort waiting and test if a value has been received
if (u64 v = jostling_value.exchange(0); !(v & bit_wait))
{
return static_cast<u32>(old2);
return static_cast<u32>(v);
}

ensure(data.bit_test_reset(off_wait));
return -1;
}

thread_ctrl::wait_on(data, bit_wait);
}
}

Expand Down