mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-08-03 11:23:49 +00:00
Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
302b579779 | ||
|
|
0b6bf01b36 | ||
|
|
66f640527d | ||
|
|
4631b96178 | ||
|
|
43f64e4ab4 | ||
|
|
e63f5b7d5c | ||
|
|
fa7c3c01bf | ||
|
|
89651f6f59 | ||
|
|
44d7f2a3e8 | ||
|
|
2dcb90066c | ||
|
|
51a33cc363 | ||
|
|
ed84370786 |
@@ -83,12 +83,12 @@ jobs:
|
||||
- name: Build
|
||||
shell: cmd
|
||||
run: |
|
||||
cmake --build _Build/windows --target launcher virtual_memory_allocation_tests --parallel
|
||||
cmake --build _Build/windows --target launcher audio_out2_port_tests virtual_memory_allocation_tests --parallel
|
||||
|
||||
- name: Test
|
||||
shell: cmd
|
||||
run: |
|
||||
ctest --test-dir _Build/windows --output-on-failure -R "^virtual_memory_allocation$"
|
||||
ctest --test-dir _Build/windows --output-on-failure -R "^(audio_out2_port|virtual_memory_allocation)$"
|
||||
|
||||
- name: Install
|
||||
shell: cmd
|
||||
@@ -159,14 +159,14 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
cmake --build _Build/macos \
|
||||
--target launcher virtual_memory_allocation_tests \
|
||||
--target launcher audio_out2_port_tests virtual_memory_allocation_tests \
|
||||
--parallel
|
||||
|
||||
- name: Test
|
||||
shell: bash
|
||||
run: |
|
||||
ctest --test-dir _Build/macos --output-on-failure \
|
||||
-R '^virtual_memory_allocation$'
|
||||
-R '^(audio_out2_port|virtual_memory_allocation)$'
|
||||
|
||||
- name: Install
|
||||
shell: bash
|
||||
@@ -297,14 +297,14 @@ jobs:
|
||||
run: |
|
||||
cmake --build _Build/linux \
|
||||
--target launcher page_manager_tests memory_tracker_tests \
|
||||
virtual_memory_allocation_tests \
|
||||
audio_out2_port_tests virtual_memory_allocation_tests \
|
||||
--parallel
|
||||
|
||||
- name: Test
|
||||
shell: bash
|
||||
run: |
|
||||
ctest --test-dir _Build/linux --output-on-failure \
|
||||
-R '^(page_manager|memory_tracker|virtual_memory_allocation)$'
|
||||
-R '^(audio_out2_port|page_manager|memory_tracker|virtual_memory_allocation)$'
|
||||
|
||||
- name: Install
|
||||
shell: bash
|
||||
|
||||
@@ -229,6 +229,17 @@ codesign --force --sign - _Build/macos/install/libMoltenVK.dylib
|
||||
|
||||
Release archives already include a signed `libMoltenVK.dylib`.
|
||||
|
||||
### Regression tests
|
||||
|
||||
Build every regression executable and run the registered tests with:
|
||||
|
||||
```powershell
|
||||
cmake --build _Build/windows --target kyty_tests
|
||||
ctest --test-dir _Build/windows --output-on-failure
|
||||
```
|
||||
|
||||
Use `_Build/linux` instead of `_Build/windows` for a Linux build.
|
||||
|
||||
### Visual Studio Code
|
||||
|
||||
A ready-made Visual Studio Code setup is included in [`.vscode`](.vscode). It configures CMake
|
||||
|
||||
+34
-1
@@ -394,6 +394,14 @@ add_executable(resource_mutex_tests EXCLUDE_FROM_ALL
|
||||
target_link_libraries(resource_mutex_tests common)
|
||||
target_include_directories(resource_mutex_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(audio_out2_port_tests EXCLUDE_FROM_ALL
|
||||
../tests/AudioOut2PortTests.cpp
|
||||
libs/libAudio2.cpp
|
||||
loader/timer.cpp
|
||||
)
|
||||
target_link_libraries(audio_out2_port_tests common fmt::fmt)
|
||||
target_include_directories(audio_out2_port_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(event_queue_lifetime_tests EXCLUDE_FROM_ALL
|
||||
../tests/EventQueueLifetimeTests.cpp
|
||||
kernel/eventQueue.cpp
|
||||
@@ -444,12 +452,17 @@ if(NOT KYTY_CLANG_CL)
|
||||
endif()
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test(NAME shader_cfg COMMAND $<TARGET_FILE:shader_cfg_tests>)
|
||||
add_test(NAME scalar_provenance COMMAND $<TARGET_FILE:scalar_provenance_tests>)
|
||||
add_test(NAME image_page_table COMMAND $<TARGET_FILE:image_page_table_tests>)
|
||||
add_test(NAME memory_tracker COMMAND $<TARGET_FILE:memory_tracker_tests>)
|
||||
add_test(NAME page_manager COMMAND $<TARGET_FILE:page_manager_tests>)
|
||||
add_test(NAME shader_vertex_metadata COMMAND $<TARGET_FILE:shader_vertex_metadata_tests>)
|
||||
add_test(NAME shader_stage_runtime COMMAND $<TARGET_FILE:shader_stage_runtime_tests>)
|
||||
add_test(NAME resource_tracking COMMAND $<TARGET_FILE:resource_tracking_tests>)
|
||||
add_test(NAME resource_mutex COMMAND $<TARGET_FILE:resource_mutex_tests>)
|
||||
add_test(NAME event_queue_lifetime COMMAND $<TARGET_FILE:event_queue_lifetime_tests>)
|
||||
add_test(NAME audio_out2_port COMMAND $<TARGET_FILE:audio_out2_port_tests>)
|
||||
add_test(NAME shader_recompiler_compute COMMAND $<TARGET_FILE:shader_recompiler_compute_tests>)
|
||||
add_test(NAME virtual_memory_allocation
|
||||
COMMAND $<TARGET_FILE:virtual_memory_allocation_tests>)
|
||||
@@ -482,6 +495,21 @@ if(BUILD_TESTING)
|
||||
add_test(NAME buffer_cache_ranges
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-range-only)
|
||||
endif()
|
||||
|
||||
add_custom_target(kyty_tests DEPENDS
|
||||
shader_cfg_tests
|
||||
scalar_provenance_tests
|
||||
image_page_table_tests
|
||||
memory_tracker_tests
|
||||
page_manager_tests
|
||||
shader_vertex_metadata_tests
|
||||
shader_stage_runtime_tests
|
||||
resource_tracking_tests
|
||||
resource_mutex_tests
|
||||
event_queue_lifetime_tests
|
||||
shader_recompiler_compute_tests
|
||||
virtual_memory_allocation_tests
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -515,7 +543,12 @@ set(KYTY_EMULATOR_MAP_LINK_PATH "${CMAKE_CURRENT_BINARY_DIR}/${KYTY_EMULATOR_MAP
|
||||
set(KYTY_EMULATOR_PDB_LINK_PATH "${CMAKE_CURRENT_BINARY_DIR}/kyty_emulator.pdb")
|
||||
|
||||
if(KYTY_CLANG_CL)
|
||||
set_target_properties(kyty_emulator PROPERTIES LINK_FLAGS "/DYNAMICBASE:NO /DEBUG:FULL /PDB:${KYTY_EMULATOR_PDB_LINK_PATH} /lldmap:${KYTY_EMULATOR_MAP_LINK_PATH}")
|
||||
target_link_options(kyty_emulator PRIVATE
|
||||
"/DYNAMICBASE:NO"
|
||||
"/DEBUG:FULL"
|
||||
"/PDB:${KYTY_EMULATOR_PDB_LINK_PATH}"
|
||||
"/lldmap:${KYTY_EMULATOR_MAP_LINK_PATH}"
|
||||
)
|
||||
add_custom_command(TARGET kyty_emulator POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" $<TARGET_FILE_DIR:kyty_emulator>/libwinpthread-1.dll)
|
||||
elseif(WIN32 OR LINUX)
|
||||
set_target_properties(kyty_emulator PROPERTIES LINK_FLAGS "${KYTY_LD_OPTIONS} -Wl,-Map=${KYTY_EMULATOR_MAP_LINK_PATH}")
|
||||
|
||||
@@ -21,6 +21,7 @@ MemoryTracker::MemoryTracker(PageManager& page_manager, PageWatchMode gpu_watch_
|
||||
|
||||
MemoryTracker::~MemoryTracker() = default;
|
||||
|
||||
#if KYTY_BUILD == KYTY_BUILD_DEBUG
|
||||
void MemoryTracker::ValidateGpuDirtyPages(const RangeSet& dirty, uint64_t vaddr, uint64_t size,
|
||||
const char* operation) const noexcept {
|
||||
if (vaddr == 0 || size == 0 || size > UINT64_MAX - vaddr ||
|
||||
@@ -58,6 +59,7 @@ void MemoryTracker::ValidateGpuDirtyOwnership(const RangeSet& dirty, uint64_t va
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void MemoryTracker::ValidateRange(uint64_t vaddr, uint64_t size) {
|
||||
if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE ||
|
||||
|
||||
@@ -71,10 +71,15 @@ public:
|
||||
EXIT("memory invalidation retained GPU-owned pages\n");
|
||||
}
|
||||
}
|
||||
#if KYTY_BUILD == KYTY_BUILD_DEBUG
|
||||
void ValidateGpuDirtyPages(const RangeSet& dirty, uint64_t vaddr, uint64_t size,
|
||||
const char* operation) const noexcept;
|
||||
void ValidateGpuDirtyOwnership(const RangeSet& dirty, uint64_t vaddr, uint64_t size,
|
||||
const char* operation);
|
||||
#else
|
||||
void ValidateGpuDirtyPages(const RangeSet&, uint64_t, uint64_t, const char*) const noexcept {}
|
||||
void ValidateGpuDirtyOwnership(const RangeSet&, uint64_t, uint64_t, const char*) {}
|
||||
#endif
|
||||
|
||||
template <bool clear, typename Preflight, typename Func>
|
||||
void ForEachDownloadRange(uint64_t vaddr, uint64_t size, Preflight&& preflight, Func&& func) {
|
||||
|
||||
@@ -871,19 +871,19 @@ std::vector<uint32_t> DominatedBlocks(const Graph& graph, uint32_t header,
|
||||
return blocks;
|
||||
}
|
||||
|
||||
uint32_t AppendSyntheticMergeBlock(Graph& graph, uint32_t old_merge) {
|
||||
const auto* merge = graph.FindBlock(old_merge);
|
||||
uint32_t AppendSyntheticBranchBlock(Graph& graph, uint32_t target) {
|
||||
const auto* target_block = graph.FindBlock(target);
|
||||
|
||||
BasicBlock block;
|
||||
block.id = static_cast<uint32_t>(graph.blocks.size());
|
||||
block.start_pc = merge != nullptr ? merge->start_pc : 0u;
|
||||
block.start_pc = target_block != nullptr ? target_block->start_pc : 0u;
|
||||
block.end_pc = block.start_pc;
|
||||
block.inst_begin = merge != nullptr ? merge->inst_begin : 0u;
|
||||
block.inst_begin = target_block != nullptr ? target_block->inst_begin : 0u;
|
||||
block.inst_end = block.inst_begin;
|
||||
block.successors = {old_merge};
|
||||
block.successors = {target};
|
||||
block.terminator.kind = TerminatorKind::Branch;
|
||||
block.terminator.condition = BranchCondition::Always;
|
||||
block.terminator.true_block = old_merge;
|
||||
block.terminator.true_block = target;
|
||||
graph.blocks.push_back(std::move(block));
|
||||
return graph.blocks.back().id;
|
||||
}
|
||||
@@ -897,15 +897,50 @@ bool IsSyntheticMergeForwarder(const Graph& graph, uint32_t block_id, uint32_t m
|
||||
block->terminator.true_block == merge;
|
||||
}
|
||||
|
||||
bool IsInsideLoopConstruct(const Graph& graph, const NaturalLoop& loop, uint32_t block_id) {
|
||||
return block_id != UINT32_MAX && block_id != loop.merge && block_id != loop.continue_block &&
|
||||
graph.Dominates(loop.header, block_id) &&
|
||||
(loop.merge == UINT32_MAX || !graph.Dominates(loop.merge, block_id));
|
||||
const NaturalLoop* FindInnermostContainingLoop(const Graph& graph, uint32_t block_id) {
|
||||
const NaturalLoop* innermost = nullptr;
|
||||
for (const auto& loop: graph.natural_loops) {
|
||||
if (Contains(loop.body_blocks, block_id) &&
|
||||
(innermost == nullptr || loop.body_blocks.size() < innermost->body_blocks.size())) {
|
||||
innermost = &loop;
|
||||
}
|
||||
}
|
||||
return innermost;
|
||||
}
|
||||
|
||||
bool SelectionMergeLeavesContainingLoop(const Graph& graph, uint32_t header, uint32_t merge) {
|
||||
bool IsInsideLoopConstruct(const Graph& graph, const NaturalLoop& loop, uint32_t block_id) {
|
||||
return block_id != UINT32_MAX && block_id != loop.merge && block_id != loop.continue_block &&
|
||||
graph.Dominates(loop.header, block_id) && !graph.Dominates(loop.merge, block_id);
|
||||
}
|
||||
|
||||
bool IsInnermostLoopControlConditional(const Graph& graph, const BasicBlock& block) {
|
||||
if (block.terminator.kind != TerminatorKind::ConditionalBranch) {
|
||||
return false;
|
||||
}
|
||||
const auto* loop = FindInnermostContainingLoop(graph, block.id);
|
||||
if (loop == nullptr || loop->merge == UINT32_MAX || loop->continue_block == UINT32_MAX) {
|
||||
return false;
|
||||
}
|
||||
const auto true_target = block.terminator.true_block;
|
||||
const auto false_target = block.terminator.false_block;
|
||||
if (block.id == loop->continue_block) {
|
||||
const auto is_repeat_target = [&](uint32_t target) {
|
||||
return target == loop->header || target == loop->merge;
|
||||
};
|
||||
return is_repeat_target(true_target) && is_repeat_target(false_target);
|
||||
}
|
||||
const auto is_control_target = [&](uint32_t target) {
|
||||
return target == loop->merge || target == loop->continue_block;
|
||||
};
|
||||
return (is_control_target(true_target) &&
|
||||
(is_control_target(false_target) ||
|
||||
IsInsideLoopConstruct(graph, *loop, false_target))) ||
|
||||
(is_control_target(false_target) && IsInsideLoopConstruct(graph, *loop, true_target));
|
||||
}
|
||||
|
||||
bool MergeLeavesContainingLoop(const Graph& graph, uint32_t header, uint32_t merge) {
|
||||
for (const auto& loop: graph.natural_loops) {
|
||||
if (IsInsideLoopConstruct(graph, loop, header) &&
|
||||
if (loop.header != header && IsInsideLoopConstruct(graph, loop, header) &&
|
||||
!IsInsideLoopConstruct(graph, loop, merge)) {
|
||||
return true;
|
||||
}
|
||||
@@ -913,6 +948,80 @@ bool SelectionMergeLeavesContainingLoop(const Graph& graph, uint32_t header, uin
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CanonicalizeNaturalLoops(Graph& graph, std::string* error) {
|
||||
const auto rewrite_budget = graph.blocks.size() * 2u + 16u;
|
||||
for (size_t rewrite = 0; rewrite < rewrite_budget; rewrite++) {
|
||||
bool changed = false;
|
||||
for (const auto& loop: graph.natural_loops) {
|
||||
std::vector<uint32_t> latches;
|
||||
for (const auto& edge: graph.back_edges) {
|
||||
if (edge.to == loop.header) {
|
||||
AddUnique(latches, edge.from);
|
||||
}
|
||||
}
|
||||
if (latches.size() <= 1u) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto continue_block = AppendSyntheticBranchBlock(graph, loop.header);
|
||||
for (auto latch: latches) {
|
||||
auto* block = graph.FindBlock(latch);
|
||||
if (block != nullptr) {
|
||||
ReplaceValue(block->successors, loop.header, continue_block);
|
||||
ReplaceTerminatorTarget(block->terminator, loop.header, continue_block);
|
||||
}
|
||||
}
|
||||
RebuildPredecessors(graph);
|
||||
RecomputeAnalyses(graph);
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
if (changed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& loop: graph.natural_loops) {
|
||||
const auto* header = graph.FindBlock(loop.header);
|
||||
const auto is_loop_control_target = [&](uint32_t target) {
|
||||
return target == loop.merge || target == loop.continue_block;
|
||||
};
|
||||
if (header == nullptr || header->terminator.kind != TerminatorKind::ConditionalBranch ||
|
||||
is_loop_control_target(header->terminator.true_block) ||
|
||||
is_loop_control_target(header->terminator.false_block) ||
|
||||
!Contains(loop.body_blocks, header->terminator.true_block) ||
|
||||
!Contains(loop.body_blocks, header->terminator.false_block)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto old_header = loop.header;
|
||||
const auto predecessors = header->predecessors;
|
||||
const auto new_header = AppendSyntheticBranchBlock(graph, old_header);
|
||||
for (auto pred: predecessors) {
|
||||
auto* block = graph.FindBlock(pred);
|
||||
if (block != nullptr) {
|
||||
ReplaceValue(block->successors, old_header, new_header);
|
||||
ReplaceTerminatorTarget(block->terminator, old_header, new_header);
|
||||
}
|
||||
}
|
||||
if (graph.entry_block == old_header) {
|
||||
graph.entry_block = new_header;
|
||||
}
|
||||
MoveBlockBefore(graph, new_header, old_header);
|
||||
RebuildPredecessors(graph);
|
||||
RecomputeAnalyses(graph);
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
if (!changed) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
SetFailure(graph, FailureKind::StructuredControlFlow, graph.entry_block,
|
||||
"CFG loop canonicalization exceeded rewrite budget", error);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SplitSharedMergeBlock(Graph& graph, uint32_t merge,
|
||||
const std::vector<uint32_t>& construct_blocks,
|
||||
bool force_split = false) {
|
||||
@@ -948,7 +1057,7 @@ bool SplitSharedMergeBlock(Graph& graph, uint32_t merge,
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto synthetic_merge = AppendSyntheticMergeBlock(graph, merge);
|
||||
const auto synthetic_merge = AppendSyntheticBranchBlock(graph, merge);
|
||||
auto* synthetic_block = graph.FindBlock(synthetic_merge);
|
||||
if (synthetic_block != nullptr) {
|
||||
synthetic_block->predecessors = predecessors_to_split;
|
||||
@@ -980,14 +1089,111 @@ bool SplitSharedMergeBlock(Graph& graph, uint32_t merge,
|
||||
bool SplitOneLoopMerge(Graph& graph) {
|
||||
const auto& loops = graph.natural_loops;
|
||||
for (const auto& loop: loops) {
|
||||
if (SplitSharedMergeBlock(graph, loop.merge, loop.body_blocks)) {
|
||||
const auto construct_blocks = DominatedBlocks(graph, loop.header, loop.merge);
|
||||
const auto force_split = MergeLeavesContainingLoop(graph, loop.header, loop.merge);
|
||||
if (SplitSharedMergeBlock(graph, loop.merge, construct_blocks, force_split)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SplitOneSelectionMerge(Graph& graph) {
|
||||
std::vector<uint32_t> SelectionRegion(const Graph& graph, const BasicBlock& header,
|
||||
uint32_t merge) {
|
||||
std::vector<uint32_t> region;
|
||||
std::vector<uint32_t> pending = {header.terminator.true_block,
|
||||
header.terminator.false_block};
|
||||
while (!pending.empty()) {
|
||||
const auto block_id = pending.back();
|
||||
pending.pop_back();
|
||||
if (block_id == merge || Contains(region, block_id)) {
|
||||
continue;
|
||||
}
|
||||
const auto* block = graph.FindBlock(block_id);
|
||||
if (block == nullptr) {
|
||||
continue;
|
||||
}
|
||||
AddUnique(region, block_id);
|
||||
pending.insert(pending.end(), block->successors.begin(), block->successors.end());
|
||||
}
|
||||
SortUnique(region);
|
||||
return region;
|
||||
}
|
||||
|
||||
bool DuplicateSelectionRegion(Graph& graph, uint32_t header_id, uint32_t merge,
|
||||
const std::vector<uint32_t>& region, uint32_t block_budget) {
|
||||
std::vector<uint32_t> cloned_blocks;
|
||||
for (auto block_id: region) {
|
||||
if (!graph.Dominates(header_id, block_id)) {
|
||||
cloned_blocks.push_back(block_id);
|
||||
}
|
||||
}
|
||||
if (cloned_blocks.empty() || graph.FindBlock(header_id) == nullptr || header_id >= merge ||
|
||||
graph.blocks.size() + cloned_blocks.size() + 1u > block_budget) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto first_clone = static_cast<uint32_t>(graph.blocks.size());
|
||||
std::map<uint32_t, uint32_t> clones;
|
||||
for (uint32_t i = 0; i < cloned_blocks.size(); i++) {
|
||||
clones.emplace(cloned_blocks[i], first_clone + i);
|
||||
}
|
||||
|
||||
for (auto block_id: cloned_blocks) {
|
||||
BasicBlock clone = *graph.FindBlock(block_id);
|
||||
clone.id = clones.at(block_id);
|
||||
clone.predecessors.clear();
|
||||
clone.dominators.clear();
|
||||
clone.post_dominators.clear();
|
||||
graph.blocks.push_back(std::move(clone));
|
||||
}
|
||||
|
||||
const auto remap_block = [&](BasicBlock& block) {
|
||||
const auto remap_target = [&](uint32_t& target) {
|
||||
if (const auto it = clones.find(target); it != clones.end()) {
|
||||
target = it->second;
|
||||
}
|
||||
};
|
||||
for (auto& successor: block.successors) {
|
||||
remap_target(successor);
|
||||
}
|
||||
remap_target(block.terminator.true_block);
|
||||
remap_target(block.terminator.false_block);
|
||||
remap_target(block.terminator.merge_block);
|
||||
remap_target(block.terminator.continue_block);
|
||||
for (auto& target: block.terminator.indirect_targets) {
|
||||
remap_target(target);
|
||||
}
|
||||
};
|
||||
for (auto block_id: region) {
|
||||
const auto owned_id = clones.contains(block_id) ? clones.at(block_id) : block_id;
|
||||
remap_block(*graph.FindBlock(owned_id));
|
||||
}
|
||||
|
||||
const auto private_merge = AppendSyntheticBranchBlock(graph, merge);
|
||||
auto& header = *graph.FindBlock(header_id);
|
||||
remap_block(header);
|
||||
|
||||
for (auto block_id: region) {
|
||||
const auto owned_id = clones.contains(block_id) ? clones.at(block_id) : block_id;
|
||||
auto* block = graph.FindBlock(owned_id);
|
||||
if (block != nullptr) {
|
||||
ReplaceValue(block->successors, merge, private_merge);
|
||||
ReplaceTerminatorTarget(block->terminator, merge, private_merge);
|
||||
}
|
||||
}
|
||||
ReplaceValue(header.successors, merge, private_merge);
|
||||
ReplaceTerminatorTarget(header.terminator, merge, private_merge);
|
||||
|
||||
for (uint32_t i = 0; i <= cloned_blocks.size(); i++) {
|
||||
MoveBlockBefore(graph, first_clone + i, merge + i);
|
||||
}
|
||||
RebuildPredecessors(graph);
|
||||
RecomputeAnalyses(graph);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SplitOneSelectionMerge(Graph& graph, uint32_t block_budget) {
|
||||
std::vector<uint32_t> loop_headers;
|
||||
loop_headers.reserve(graph.natural_loops.size());
|
||||
for (const auto& loop: graph.natural_loops) {
|
||||
@@ -1001,11 +1207,26 @@ bool SplitOneSelectionMerge(Graph& graph) {
|
||||
Contains(loop_headers, block_id)) {
|
||||
continue;
|
||||
}
|
||||
if (IsInnermostLoopControlConditional(graph, *block)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto merge = graph.FindNearestCommonPostDominator(block->terminator.true_block,
|
||||
block->terminator.false_block);
|
||||
if (merge == UINT32_MAX || graph.FindBlock(merge) == nullptr) {
|
||||
continue;
|
||||
}
|
||||
const auto region = SelectionRegion(graph, *block, merge);
|
||||
if (std::any_of(region.begin(), region.end(),
|
||||
[&](uint32_t member) { return !graph.Dominates(block_id, member); })) {
|
||||
if (graph.natural_loops.empty() &&
|
||||
DuplicateSelectionRegion(graph, block_id, merge, region, block_budget)) {
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const auto construct_blocks = DominatedBlocks(graph, block_id, merge);
|
||||
const auto force_split = SelectionMergeLeavesContainingLoop(graph, block_id, merge);
|
||||
const auto force_split = MergeLeavesContainingLoop(graph, block_id, merge);
|
||||
if (SplitSharedMergeBlock(graph, merge, construct_blocks, force_split)) {
|
||||
return true;
|
||||
}
|
||||
@@ -1015,10 +1236,12 @@ bool SplitOneSelectionMerge(Graph& graph) {
|
||||
|
||||
bool SplitSharedMergeBlocks(Graph& graph, std::string* error) {
|
||||
const auto original_block_count = static_cast<uint32_t>(graph.blocks.size());
|
||||
const auto split_budget =
|
||||
std::max<uint32_t>(16u, std::min<uint32_t>(128u, original_block_count));
|
||||
const auto split_budget = std::max<uint32_t>(
|
||||
16u, std::min<uint32_t>(128u, original_block_count * 4u));
|
||||
const auto block_budget = std::max<uint32_t>(
|
||||
32u, std::min<uint32_t>(512u, original_block_count * 8u));
|
||||
for (uint32_t splits = 0; splits < split_budget; splits++) {
|
||||
if (!SplitOneLoopMerge(graph) && !SplitOneSelectionMerge(graph)) {
|
||||
if (!SplitOneLoopMerge(graph) && !SplitOneSelectionMerge(graph, block_budget)) {
|
||||
return true;
|
||||
}
|
||||
RebuildPredecessors(graph);
|
||||
@@ -1353,6 +1576,9 @@ bool Structurize(Graph& graph, std::string* error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CanonicalizeNaturalLoops(graph, error)) {
|
||||
return false;
|
||||
}
|
||||
if (!SplitSharedMergeBlocks(graph, error)) {
|
||||
return false;
|
||||
}
|
||||
@@ -1395,6 +1621,9 @@ bool Structurize(Graph& graph, std::string* error) {
|
||||
block.terminator.loop_header) {
|
||||
continue;
|
||||
}
|
||||
if (IsInnermostLoopControlConditional(graph, block)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto merge = graph.FindNearestCommonPostDominator(block.terminator.true_block,
|
||||
block.terminator.false_block);
|
||||
|
||||
@@ -1058,6 +1058,76 @@ private:
|
||||
{HOST_SYSTEM_RESERVED_MIN, HOST_SYSTEM_RESERVED_MAX + 1u},
|
||||
{HOST_USER_MIN, HOST_USER_MAX + 1u},
|
||||
}};
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX && !defined(__APPLE__)
|
||||
std::vector<std::pair<uint64_t, uint64_t>> occupied;
|
||||
FILE* maps = fopen("/proc/self/maps", "r");
|
||||
EXIT_IF(maps == nullptr);
|
||||
|
||||
char line[512];
|
||||
while (fgets(line, sizeof(line), maps) != nullptr) {
|
||||
unsigned long long mapping_start = 0;
|
||||
unsigned long long mapping_end = 0;
|
||||
if (sscanf(line, "%llx-%llx", &mapping_start, &mapping_end) == 2) {
|
||||
occupied.emplace_back(static_cast<uint64_t>(mapping_start),
|
||||
static_cast<uint64_t>(mapping_end));
|
||||
}
|
||||
}
|
||||
fclose(maps);
|
||||
|
||||
auto reserve_range = [this](uint64_t start, uint64_t end) {
|
||||
start = AlignUp(start, PageSize());
|
||||
end = AlignDown(end, PageSize());
|
||||
if (start == 0 || end <= start) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto size = end - start;
|
||||
int flags = MAP_PRIVATE | MAP_ANON | MAP_NORESERVE;
|
||||
#if defined(KYTY_LINKED_GUEST_ADDRESS_SPACE)
|
||||
flags |= MAP_FIXED;
|
||||
#elif defined(MAP_FIXED_NOREPLACE)
|
||||
flags |= MAP_FIXED_NOREPLACE;
|
||||
#endif
|
||||
|
||||
void* ptr = mmap(reinterpret_cast<void*>(start), size, PROT_NONE, flags, -1, 0);
|
||||
if (ptr == MAP_FAILED || reinterpret_cast<uint64_t>(ptr) != start) {
|
||||
if (ptr != MAP_FAILED) {
|
||||
munmap(ptr, size);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
AddFreeUnlocked(start, size);
|
||||
m_owned.emplace_back(start, size);
|
||||
};
|
||||
|
||||
for (const auto& [region_start, region_end]: regions) {
|
||||
auto current = region_start;
|
||||
|
||||
for (const auto& [mapping_start, mapping_end]: occupied) {
|
||||
if (mapping_end <= current) {
|
||||
continue;
|
||||
}
|
||||
if (mapping_start >= region_end) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (mapping_start > current) {
|
||||
reserve_range(current, std::min(mapping_start, region_end));
|
||||
}
|
||||
|
||||
current = std::max(current, mapping_end);
|
||||
if (current >= region_end) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (current < region_end) {
|
||||
reserve_range(current, region_end);
|
||||
}
|
||||
}
|
||||
#else
|
||||
for (const auto& [start, end]: regions) {
|
||||
int flags = MAP_PRIVATE | MAP_ANON | MAP_NORESERVE;
|
||||
#if defined(KYTY_LINKED_GUEST_ADDRESS_SPACE)
|
||||
@@ -1070,13 +1140,14 @@ private:
|
||||
if (ptr != MAP_FAILED) {
|
||||
munmap(ptr, end - start);
|
||||
}
|
||||
EXIT("failed to reserve guest address space at 0x%016" PRIx64 ", size 0x%016" PRIx64
|
||||
"\n",
|
||||
EXIT("failed to reserve guest address space at 0x%016" PRIx64
|
||||
", size 0x%016" PRIx64 "\n",
|
||||
start, end - start);
|
||||
}
|
||||
AddFreeUnlocked(start, end - start);
|
||||
m_owned.emplace_back(start, end - start);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
+12
-2
@@ -65,6 +65,10 @@
|
||||
|
||||
namespace Libs {
|
||||
|
||||
namespace LibcInternalExt {
|
||||
void RunThreadAtexitDestructors();
|
||||
} // namespace LibcInternalExt
|
||||
|
||||
namespace LibKernel {
|
||||
|
||||
LIB_NAME("libkernel", "libkernel");
|
||||
@@ -3347,6 +3351,8 @@ int PthreadGetCurrentPriorityForKernel() {
|
||||
static void CleanupThread(void* arg) {
|
||||
auto* thread = static_cast<Pthread>(arg);
|
||||
|
||||
LibcInternalExt::RunThreadAtexitDestructors();
|
||||
|
||||
auto thread_dtors = g_pthread_context->GetThreadDtors();
|
||||
|
||||
if (thread_dtors != nullptr) {
|
||||
@@ -3880,8 +3886,12 @@ int KYTY_SYSV_ABI KernelGettimeofday(KernelTimeval* tp) {
|
||||
tp->tv_sec = static_cast<int64_t>(ticks / 1000000);
|
||||
tp->tv_usec = static_cast<int64_t>(ticks % 1000000);
|
||||
#else
|
||||
auto dt = Common::DateTime::FromSystemUTC();
|
||||
sec_to_timeval(tp, dt.ToUnix());
|
||||
struct timespec ts {};
|
||||
result = ::clock_gettime(CLOCK_REALTIME, &ts);
|
||||
if (result == 0) {
|
||||
tp->tv_sec = static_cast<int64_t>(ts.tv_sec);
|
||||
tp->tv_usec = static_cast<int64_t>(ts.tv_nsec / 1000);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (result == 0) {
|
||||
|
||||
+16
-2
@@ -364,7 +364,12 @@ bool Audio::QueueSdlAudio(PortOut* port, const void* data, bool blocking) {
|
||||
}
|
||||
|
||||
if (blocking) {
|
||||
const auto min_queued_size = queue_size * 2u;
|
||||
constexpr uint64_t target_latency_us = 40000;
|
||||
const auto buffer_us = port->freq != 0 ? (1000000ULL * port->samples_num) / port->freq : 0;
|
||||
const auto buffers =
|
||||
buffer_us != 0 ? static_cast<uint32_t>((target_latency_us + buffer_us - 1) / buffer_us)
|
||||
: 2u;
|
||||
const auto min_queued_size = queue_size * std::clamp(buffers, 2u, 16u);
|
||||
const auto wait_start = LibKernel::KernelGetProcessTime();
|
||||
while (SDL_GetQueuedAudioSize(port->audio_device) > min_queued_size) {
|
||||
if (LibKernel::KernelGetProcessTime() - wait_start > 200000) {
|
||||
@@ -511,7 +516,16 @@ uint32_t Audio::AudioOutOutputs(OutputParam* params, uint32_t num, bool blocking
|
||||
max_wait_time = (wait_time > max_wait_time ? wait_time : max_wait_time);
|
||||
}
|
||||
|
||||
if (blocking && max_wait_time != 0) {
|
||||
bool all_ports_have_device = true;
|
||||
for (uint32_t i = 0; i < num; i++) {
|
||||
if (m_out_ports[params[i].handle.GetId()].audio_device == 0) {
|
||||
all_ports_have_device = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Device-backed ports are paced by the SDL queue above.
|
||||
if (blocking && max_wait_time != 0 && !all_ports_have_device) {
|
||||
Common::Thread::SleepMicro(max_wait_time);
|
||||
}
|
||||
|
||||
|
||||
+46
-43
@@ -336,18 +336,6 @@ static AudioOut2PortStateEntry* audioout2_find_port_locked(AudioOut2PortHandle p
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static uint32_t audioout2_context_grains(AudioOut2ContextHandle ctx) {
|
||||
uint32_t samples_num = 512;
|
||||
|
||||
g_audioout2_context_mutex.Lock();
|
||||
if (auto* state = audioout2_find_context_locked(ctx); state != nullptr) {
|
||||
samples_num = (state->num_grains == 0 ? 512u : state->num_grains);
|
||||
}
|
||||
g_audioout2_context_mutex.Unlock();
|
||||
|
||||
return samples_num;
|
||||
}
|
||||
|
||||
static void audioout2_queue_context_audio(AudioOut2ContextHandle ctx, bool blocking) {
|
||||
std::vector<AudioInternal::OutputParam> params;
|
||||
params.reserve(AudioInternal::OUT_PORTS_MAX);
|
||||
@@ -455,6 +443,12 @@ int KYTY_SYSV_ABI AudioOut2ContextDestroy(AudioOut2ContextHandle ctx) {
|
||||
PRINT_NAME();
|
||||
LOGF("\t ctx = 0x%016" PRIx64 "\n", ctx);
|
||||
|
||||
g_audioout2_context_mutex.Lock();
|
||||
if (auto* state = audioout2_find_context_locked(ctx); state != nullptr) {
|
||||
*state = AudioOut2ContextState {};
|
||||
}
|
||||
g_audioout2_context_mutex.Unlock();
|
||||
|
||||
std::array<int, 256> audio_handles {};
|
||||
size_t audio_handles_num = 0;
|
||||
|
||||
@@ -473,12 +467,6 @@ int KYTY_SYSV_ABI AudioOut2ContextDestroy(AudioOut2ContextHandle ctx) {
|
||||
audioout2_close_audio_handle(audio_handles[i]);
|
||||
}
|
||||
|
||||
g_audioout2_context_mutex.Lock();
|
||||
if (auto* state = audioout2_find_context_locked(ctx); state != nullptr) {
|
||||
*state = AudioOut2ContextState {};
|
||||
}
|
||||
g_audioout2_context_mutex.Unlock();
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
@@ -559,30 +547,45 @@ int KYTY_SYSV_ABI AudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut
|
||||
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
||||
EXIT_NOT_IMPLEMENTED(port == nullptr);
|
||||
|
||||
const auto next_port = g_audioout2_next_port.fetch_add(1, std::memory_order_relaxed);
|
||||
const auto next_port = g_audioout2_next_port.fetch_add(1, std::memory_order_relaxed);
|
||||
const auto audio_format = audioout2_data_format_to_audio_format(params->data_format);
|
||||
const auto audio_type = audioout2_port_type_to_audio_out_type(params->port_type);
|
||||
|
||||
g_audioout2_context_mutex.Lock();
|
||||
const auto* context_state = audioout2_find_context_locked(ctx);
|
||||
if (context_state == nullptr) {
|
||||
g_audioout2_context_mutex.Unlock();
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
}
|
||||
const auto samples_num = context_state->num_grains == 0 ? 512u : context_state->num_grains;
|
||||
|
||||
g_audioout2_port_mutex.Lock();
|
||||
auto* port_state = audioout2_find_port_locked(0);
|
||||
if (port_state == nullptr) {
|
||||
for (auto& candidate: g_audioout2_ports) {
|
||||
if (!candidate.used) {
|
||||
port_state = &candidate;
|
||||
break;
|
||||
}
|
||||
AudioOut2PortStateEntry* port_state = nullptr;
|
||||
for (auto& candidate: g_audioout2_ports) {
|
||||
if (!candidate.used) {
|
||||
port_state = &candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (port_state != nullptr) {
|
||||
*port_state = AudioOut2PortStateEntry {};
|
||||
port_state->used = true;
|
||||
port_state->handle = next_port;
|
||||
port_state->context = ctx;
|
||||
port_state->port_type = params->port_type;
|
||||
port_state->data_format = params->data_format;
|
||||
port_state->sampling_freq = params->sampling_freq;
|
||||
port_state->samples_num = samples_num;
|
||||
port_state->audio_format = audio_format;
|
||||
}
|
||||
g_audioout2_port_mutex.Unlock();
|
||||
g_audioout2_context_mutex.Unlock();
|
||||
|
||||
if (next_port > g_audioout2_ports.size() || port_state == nullptr) {
|
||||
if (port_state == nullptr) {
|
||||
return AUDIO_OUT2_ERROR_PORT_FULL;
|
||||
}
|
||||
|
||||
*port = next_port;
|
||||
|
||||
const auto samples_num = audioout2_context_grains(ctx);
|
||||
const auto audio_format = audioout2_data_format_to_audio_format(params->data_format);
|
||||
const auto audio_type = audioout2_port_type_to_audio_out_type(params->port_type);
|
||||
int audio_handle = 0;
|
||||
int audio_handle = 0;
|
||||
|
||||
if (audio_format != AudioInternal::Format::Unknown &&
|
||||
!audioout2_port_type_is_object(params->port_type)) {
|
||||
@@ -591,17 +594,17 @@ int KYTY_SYSV_ABI AudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut
|
||||
}
|
||||
|
||||
g_audioout2_port_mutex.Lock();
|
||||
*port_state = AudioOut2PortStateEntry {};
|
||||
port_state->used = true;
|
||||
port_state->handle = *port;
|
||||
port_state->context = ctx;
|
||||
port_state->port_type = params->port_type;
|
||||
port_state->data_format = params->data_format;
|
||||
port_state->sampling_freq = params->sampling_freq;
|
||||
port_state->samples_num = samples_num;
|
||||
port_state->audio_format = audio_format;
|
||||
port_state->audio_handle = audio_handle;
|
||||
const bool reserved = port_state->used && port_state->handle == next_port;
|
||||
if (reserved) {
|
||||
port_state->audio_handle = audio_handle;
|
||||
}
|
||||
g_audioout2_port_mutex.Unlock();
|
||||
if (!reserved) {
|
||||
audioout2_close_audio_handle(audio_handle);
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
*port = next_port;
|
||||
|
||||
if (next_port <= 16 || (next_port % 600) == 0) {
|
||||
PRINT_NAME();
|
||||
|
||||
+24
-11
@@ -633,6 +633,15 @@ LIB_VERSION("LibcInternalExt", 1, "LibcInternal", 1, 1);
|
||||
static uint64_t g_mspace_atomic_id_mask = 0;
|
||||
static uint64_t g_mstate_table[64] = {0};
|
||||
|
||||
using thread_atexit_destructor_t = KYTY_SYSV_ABI void (*)(void*);
|
||||
|
||||
struct ThreadAtexitDestructor {
|
||||
thread_atexit_destructor_t destructor;
|
||||
void* object;
|
||||
};
|
||||
|
||||
static thread_local std::vector<ThreadAtexitDestructor> g_thread_atexit_destructors;
|
||||
|
||||
struct Info {
|
||||
uint64_t size;
|
||||
uint32_t unknown1;
|
||||
@@ -650,25 +659,29 @@ void KYTY_SYSV_ABI LibcHeapGetTraceInfo(Info* info) {
|
||||
info->mstate_table = g_mstate_table;
|
||||
}
|
||||
|
||||
uint64_t KYTY_SYSV_ABI LibcInternalExtUnknownQBS714Jr3g(uint64_t arg0, uint64_t arg1, uint64_t arg2,
|
||||
uint64_t arg3, uint64_t arg4,
|
||||
uint64_t arg5) {
|
||||
int KYTY_SYSV_ABI LibcInternalExtCxaThreadAtexit(thread_atexit_destructor_t destructor, void* object,
|
||||
void* /*module_id*/) {
|
||||
PRINT_NAME();
|
||||
|
||||
LOGF("\t arg0 = 0x%016" PRIx64 "\n"
|
||||
"\t arg1 = 0x%016" PRIx64 "\n"
|
||||
"\t arg2 = 0x%016" PRIx64 "\n"
|
||||
"\t arg3 = 0x%016" PRIx64 "\n"
|
||||
"\t arg4 = 0x%016" PRIx64 "\n"
|
||||
"\t arg5 = 0x%016" PRIx64 "\n",
|
||||
arg0, arg1, arg2, arg3, arg4, arg5);
|
||||
g_thread_atexit_destructors.push_back({destructor, object});
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void RunThreadAtexitDestructors() {
|
||||
while (!g_thread_atexit_destructors.empty()) {
|
||||
auto destructor = g_thread_atexit_destructors.back();
|
||||
g_thread_atexit_destructors.pop_back();
|
||||
|
||||
if (destructor.destructor != nullptr) {
|
||||
destructor.destructor(destructor.object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LIB_DEFINE(InitLibcInternalExt_1) {
|
||||
LIB_FUNC("NWtTN10cJzE", LibcInternalExt::LibcHeapGetTraceInfo);
|
||||
LIB_FUNC("qBS714-Jr3g", LibcInternalExt::LibcInternalExtUnknownQBS714Jr3g);
|
||||
LIB_FUNC("qBS714-Jr3g", LibcInternalExt::LibcInternalExtCxaThreadAtexit);
|
||||
}
|
||||
|
||||
} // namespace LibcInternalExt
|
||||
|
||||
+7
-14
@@ -4,6 +4,7 @@
|
||||
#include "libs/libs.h"
|
||||
#include "loader/symbolDatabase.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
@@ -247,20 +248,12 @@ static int KYTY_SYSV_ABI RtcGetCurrentTick(RtcTick* tick) {
|
||||
return RTC_ERROR_DATETIME_UNINITIALIZED;
|
||||
}
|
||||
|
||||
const auto now = Common::DateTime::FromSystemUTC();
|
||||
const auto date = now.GetDate();
|
||||
const auto tod = now.GetTime();
|
||||
|
||||
RtcDateTime time {};
|
||||
time.year = static_cast<uint16_t>(date.Year());
|
||||
time.month = static_cast<uint16_t>(date.Month());
|
||||
time.day = static_cast<uint16_t>(date.Day());
|
||||
time.hour = static_cast<uint16_t>(tod.Hour24());
|
||||
time.minute = static_cast<uint16_t>(tod.Minute());
|
||||
time.second = static_cast<uint16_t>(tod.Second());
|
||||
time.microsecond = static_cast<uint32_t>(tod.Msec() * 1000);
|
||||
|
||||
return RtcGetTick(&time, tick);
|
||||
const auto now_us =
|
||||
static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count());
|
||||
tick->tick = RTC_UNIX_EPOCH_TICKS + now_us;
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI RtcGetCurrentNetworkTick(RtcTick* tick) {
|
||||
|
||||
@@ -148,7 +148,7 @@ static bool PatchGuestMemory64(uint64_t vaddr, uint64_t value) {
|
||||
}
|
||||
|
||||
static uint64_t AllocateUnresolvedImportThunk(uint64_t record_id) {
|
||||
constexpr uint64_t thunk_size = 162;
|
||||
constexpr uint64_t thunk_size = 165;
|
||||
|
||||
if (g_unresolved_stub_thunk_pages.empty() ||
|
||||
g_unresolved_stub_thunk_offset + thunk_size > UNRESOLVED_STUB_PAGE_SIZE) {
|
||||
@@ -254,6 +254,10 @@ static uint64_t AllocateUnresolvedImportThunk(uint64_t record_id) {
|
||||
emit(0x41);
|
||||
emit(0xff);
|
||||
emit(0xe3); // jmp r11
|
||||
// Match the integer fallback for floating-point return values.
|
||||
emit(0x0f);
|
||||
emit(0x57);
|
||||
emit(0xc0); // xorps xmm0, xmm0
|
||||
emit(0x31);
|
||||
emit(0xc0); // xor eax, eax
|
||||
emit(0xc3); // ret
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
#include "libs/audio.h"
|
||||
#include "libs/audio_internal.h"
|
||||
#include "libs/errno.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
namespace AudioOut2 = Libs::Audio::AudioOut2;
|
||||
|
||||
std::mutex g_device_mutex;
|
||||
std::condition_variable g_device_cv;
|
||||
std::vector<int> g_live_devices;
|
||||
int g_next_device = 1;
|
||||
int g_open_waiters = 0;
|
||||
bool g_block_opens = false;
|
||||
|
||||
void Check(bool value, const char* text) {
|
||||
if (!value) {
|
||||
std::fprintf(stderr, "AudioOut2PortTests: failed: %s\n", text);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
struct PortParam {
|
||||
uint16_t port_type;
|
||||
uint16_t pad;
|
||||
uint32_t data_format;
|
||||
uint32_t sampling_freq;
|
||||
uint32_t flags;
|
||||
uint64_t user_handle;
|
||||
uint32_t reserved[10];
|
||||
};
|
||||
|
||||
struct ContextParam {
|
||||
uint32_t max_ports;
|
||||
uint32_t max_object_ports;
|
||||
uint32_t guarantee_object_ports;
|
||||
uint32_t queue_depth;
|
||||
uint32_t num_grains;
|
||||
uint32_t flags;
|
||||
uint32_t reserved[10];
|
||||
};
|
||||
|
||||
struct PortState {
|
||||
uint16_t output;
|
||||
uint8_t num_channels;
|
||||
uint8_t pad1;
|
||||
int16_t volume;
|
||||
uint16_t reroute_counter;
|
||||
uint32_t flags;
|
||||
uint32_t pad2;
|
||||
uint64_t reserved[6];
|
||||
};
|
||||
|
||||
const auto* AsParam(const PortParam* param) {
|
||||
return reinterpret_cast<const AudioOut2::AudioOut2PortParam*>(param);
|
||||
}
|
||||
|
||||
const auto* AsParam(const ContextParam* param) {
|
||||
return reinterpret_cast<const AudioOut2::AudioOut2ContextParam*>(param);
|
||||
}
|
||||
|
||||
auto* AsState(PortState* state) {
|
||||
return reinterpret_cast<AudioOut2::AudioOut2PortState*>(state);
|
||||
}
|
||||
|
||||
PortParam MakeParam(uint32_t data_format = 0x200) {
|
||||
PortParam param {};
|
||||
param.data_format = data_format;
|
||||
param.sampling_freq = 48000;
|
||||
return param;
|
||||
}
|
||||
|
||||
AudioOut2::AudioOut2ContextHandle CreateContext() {
|
||||
ContextParam param {};
|
||||
param.queue_depth = 4;
|
||||
param.num_grains = 512;
|
||||
AudioOut2::AudioOut2ContextHandle context = 0;
|
||||
Check(AudioOut2::AudioOut2ContextCreate(AsParam(¶m), nullptr, 0, &context) == OK,
|
||||
"context create failed");
|
||||
return context;
|
||||
}
|
||||
|
||||
void BlockDeviceOpens() {
|
||||
std::lock_guard lock(g_device_mutex);
|
||||
g_open_waiters = 0;
|
||||
g_block_opens = true;
|
||||
}
|
||||
|
||||
void WaitForDeviceOpens(int count) {
|
||||
std::unique_lock lock(g_device_mutex);
|
||||
g_device_cv.wait(lock, [count]() { return g_open_waiters >= count; });
|
||||
}
|
||||
|
||||
void ReleaseDeviceOpens() {
|
||||
std::lock_guard lock(g_device_mutex);
|
||||
g_block_opens = false;
|
||||
g_device_cv.notify_all();
|
||||
}
|
||||
|
||||
int LiveDeviceCount() {
|
||||
std::lock_guard lock(g_device_mutex);
|
||||
return static_cast<int>(g_live_devices.size());
|
||||
}
|
||||
|
||||
void TestSlotReuse() {
|
||||
const auto context = CreateContext();
|
||||
const auto param = MakeParam();
|
||||
for (int i = 0; i < 300; i++) {
|
||||
AudioOut2::AudioOut2PortHandle port = 0;
|
||||
Check(AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &port) == OK,
|
||||
"port slot was not reusable");
|
||||
Check(port != 0, "port handle is zero");
|
||||
AudioOut2::AudioOut2PortDestroy(port);
|
||||
}
|
||||
AudioOut2::AudioOut2ContextDestroy(context);
|
||||
}
|
||||
|
||||
void TestFullTableRecovers() {
|
||||
const auto context = CreateContext();
|
||||
const auto param = MakeParam();
|
||||
std::vector<AudioOut2::AudioOut2PortHandle> ports;
|
||||
ports.reserve(256);
|
||||
|
||||
for (int i = 0; i < 256; i++) {
|
||||
AudioOut2::AudioOut2PortHandle port = 0;
|
||||
Check(AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &port) == OK,
|
||||
"port table filled early");
|
||||
ports.push_back(port);
|
||||
}
|
||||
|
||||
AudioOut2::AudioOut2PortHandle overflow = 0;
|
||||
Check(AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &overflow) != OK,
|
||||
"full port table accepted another port");
|
||||
|
||||
for (auto port: ports) {
|
||||
AudioOut2::AudioOut2PortDestroy(port);
|
||||
}
|
||||
|
||||
AudioOut2::AudioOut2PortHandle port = 0;
|
||||
Check(AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &port) == OK,
|
||||
"port table did not recover");
|
||||
AudioOut2::AudioOut2PortDestroy(port);
|
||||
AudioOut2::AudioOut2ContextDestroy(context);
|
||||
}
|
||||
|
||||
void TestConcurrentCreates() {
|
||||
constexpr int thread_count = 8;
|
||||
const auto context = CreateContext();
|
||||
const auto param = MakeParam(0x800);
|
||||
std::vector<AudioOut2::AudioOut2PortHandle> ports(thread_count);
|
||||
std::vector<int> results(thread_count);
|
||||
std::vector<std::thread> threads;
|
||||
|
||||
BlockDeviceOpens();
|
||||
for (int i = 0; i < thread_count; i++) {
|
||||
threads.emplace_back([&, i]() {
|
||||
results[i] = AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &ports[i]);
|
||||
});
|
||||
}
|
||||
WaitForDeviceOpens(thread_count);
|
||||
ReleaseDeviceOpens();
|
||||
for (auto& thread: threads) {
|
||||
thread.join();
|
||||
}
|
||||
|
||||
for (int i = 0; i < thread_count; i++) {
|
||||
Check(results[i] == OK, "concurrent port create failed");
|
||||
PortState state {};
|
||||
AudioOut2::AudioOut2PortGetState(ports[i], AsState(&state));
|
||||
Check(state.num_channels == 8, "concurrent create lost its reserved slot");
|
||||
AudioOut2::AudioOut2PortDestroy(ports[i]);
|
||||
}
|
||||
Check(LiveDeviceCount() == 0, "concurrent create leaked a device");
|
||||
AudioOut2::AudioOut2ContextDestroy(context);
|
||||
}
|
||||
|
||||
void TestContextDestroyCancelsPendingCreate() {
|
||||
const auto context = CreateContext();
|
||||
const auto param = MakeParam();
|
||||
AudioOut2::AudioOut2PortHandle port = 0;
|
||||
int result = OK;
|
||||
|
||||
BlockDeviceOpens();
|
||||
std::thread creator(
|
||||
[&]() { result = AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &port); });
|
||||
WaitForDeviceOpens(1);
|
||||
AudioOut2::AudioOut2ContextDestroy(context);
|
||||
ReleaseDeviceOpens();
|
||||
creator.join();
|
||||
|
||||
Check(result != OK, "destroyed context retained a pending port create");
|
||||
Check(LiveDeviceCount() == 0, "cancelled port create leaked a device");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Libs::Audio::AudioInternal {
|
||||
|
||||
int AudioOutOpen(int /*type*/, uint32_t /*samples_num*/, uint32_t /*freq*/, Format /*format*/) {
|
||||
std::unique_lock lock(g_device_mutex);
|
||||
const int handle = g_next_device++;
|
||||
g_live_devices.push_back(handle);
|
||||
g_open_waiters++;
|
||||
g_device_cv.notify_all();
|
||||
g_device_cv.wait(lock, []() { return !g_block_opens; });
|
||||
return handle;
|
||||
}
|
||||
|
||||
void AudioOutClose(int handle) {
|
||||
std::lock_guard lock(g_device_mutex);
|
||||
const auto it = std::find(g_live_devices.begin(), g_live_devices.end(), handle);
|
||||
if (it != g_live_devices.end()) {
|
||||
g_live_devices.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t AudioOutOutputs(const OutputParam* /*params*/, uint32_t /*num*/, bool /*blocking*/) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace Libs::Audio::AudioInternal
|
||||
|
||||
namespace Libs::LibKernel {
|
||||
|
||||
uint64_t KYTY_SYSV_ABI KernelGetProcessTime() {
|
||||
static std::atomic_uint64_t now {0};
|
||||
return now.fetch_add(1000);
|
||||
}
|
||||
|
||||
} // namespace Libs::LibKernel
|
||||
|
||||
int main() {
|
||||
TestSlotReuse();
|
||||
TestFullTableRecovers();
|
||||
TestConcurrentCreates();
|
||||
TestContextDestroyCancelsPendingCreate();
|
||||
std::printf("AudioOut2PortTests: all cases passed\n");
|
||||
return 0;
|
||||
}
|
||||
+413
-12
@@ -5394,7 +5394,224 @@ void TestNewShaderRecompilerCfgSharedOuterAndLoopMerge() {
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerCfgLoopSharedContinueSelectionMerges() {
|
||||
void TestNewShaderRecompilerCfgLoopEarlyBreakNoSelection() {
|
||||
const uint32_t shader[] = {
|
||||
EncodeSopc(0x0a, 0, 129), // loop: s_cmp_lt_u32 s0, 1
|
||||
EncodeSopp(0x04, 4), // loop exit -> end
|
||||
EncodeSopc(0x06, 1, 1), // s_cmp_eq_u32 s1, s1
|
||||
EncodeSopp(0x04, 2), // early break -> same loop end
|
||||
EncodeSop2(0x00, 0, 0, 129), // s_add_u32 s0, s0, 1
|
||||
EncodeSopp(0x02, 0xfffau), // backedge -> loop header
|
||||
0xbf810000u,
|
||||
};
|
||||
|
||||
ShaderRecompiler::CompileOptions options;
|
||||
options.stage = ShaderType::Compute;
|
||||
options.dump_ir = true;
|
||||
|
||||
ShaderRecompiler::CompileResult result;
|
||||
std::string error;
|
||||
Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
|
||||
Check(Common::ContainsStr(result.ir_dump, "mode=structured"),
|
||||
"loop early-break CFG did not stay on structured path");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 246) != 0,
|
||||
"loop early-break SPIR-V lacks OpLoopMerge");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 247) == 0,
|
||||
"loop early-break SPIR-V unexpectedly used OpSelectionMerge");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 251) == 0,
|
||||
"loop early-break CFG unexpectedly used dispatcher OpSwitch");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerCfgNestedLoopNonlocalExitDispatcher() {
|
||||
const uint32_t shader[] = {
|
||||
EncodeSopc(0x0a, 0, 129), // outer loop: s_cmp_lt_u32 s0, 1
|
||||
EncodeSopp(0x04, 9), // outer exit -> end
|
||||
EncodeSopc(0x0a, 1, 129), // inner loop: s_cmp_lt_u32 s1, 1
|
||||
EncodeSopp(0x04, 5), // inner exit -> outer continue
|
||||
EncodeSopc(0x06, 2, 2), // s_cmp_eq_u32 s2, s2
|
||||
EncodeSopp(0x05, 5), // nonlocal exit -> outer end
|
||||
EncodeSMovB32(3, 129), // inner work
|
||||
EncodeSop2(0x00, 1, 1, 129), // s_add_u32 s1, s1, 1
|
||||
EncodeSopp(0x02, 0xfff9u), // inner backedge
|
||||
EncodeSop2(0x00, 0, 0, 129), // outer continue: s_add_u32 s0, s0, 1
|
||||
EncodeSopp(0x02, 0xfff5u), // outer backedge
|
||||
0xbf810000u,
|
||||
};
|
||||
|
||||
ShaderRecompiler::CompileOptions options;
|
||||
options.stage = ShaderType::Compute;
|
||||
options.dump_ir = true;
|
||||
|
||||
ShaderRecompiler::CompileResult result;
|
||||
std::string error;
|
||||
Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
|
||||
Check(Common::ContainsStr(result.ir_dump, "mode=dispatcher"),
|
||||
"nested-loop nonlocal exit did not select dispatcher fallback");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 251) != 0,
|
||||
"nested-loop nonlocal exit dispatcher SPIR-V lacks OpSwitch");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerCfgNestedLoopLocalExitNoSelection() {
|
||||
const uint32_t shader[] = {
|
||||
EncodeSopc(0x0a, 0, 129), // outer loop: s_cmp_lt_u32 s0, 1
|
||||
EncodeSopp(0x04, 6), // outer exit -> end
|
||||
EncodeSopc(0x0a, 1, 129), // inner loop: s_cmp_lt_u32 s1, 1
|
||||
EncodeSopp(0x04, 2), // inner exit -> outer continue
|
||||
EncodeSMovB32(2, 129), // inner work
|
||||
EncodeSopp(0x02, 0xfffcu), // inner backedge
|
||||
EncodeSop2(0x00, 0, 0, 129), // outer continue: s_add_u32 s0, s0, 1
|
||||
EncodeSopp(0x02, 0xfff8u), // outer backedge
|
||||
0xbf810000u,
|
||||
};
|
||||
|
||||
ShaderRecompiler::CompileOptions options;
|
||||
options.stage = ShaderType::Compute;
|
||||
options.dump_ir = true;
|
||||
|
||||
ShaderRecompiler::CompileResult result;
|
||||
std::string error;
|
||||
Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
|
||||
Check(Common::ContainsStr(result.ir_dump, "mode=structured"),
|
||||
"nested local loop exit did not stay on structured path");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 246) >= 2,
|
||||
"nested local loop exit SPIR-V lacks both OpLoopMerge instructions");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 247) == 0,
|
||||
"nested local loop exit SPIR-V unexpectedly used OpSelectionMerge");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 251) == 0,
|
||||
"nested local loop exit unexpectedly used dispatcher OpSwitch");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerCfgNestedLoopExitTailMergeSplit() {
|
||||
const uint32_t shader[] = {
|
||||
EncodeSopc(0x0a, 0, 129), // outer loop: s_cmp_lt_u32 s0, 1
|
||||
EncodeSopp(0x04, 11), // outer exit -> end
|
||||
EncodeSopc(0x06, 1, 1), // inner loop first exit condition
|
||||
EncodeSopp(0x05, 3), // first inner exit -> tail A
|
||||
EncodeSopc(0x06, 2, 2), // inner loop second exit condition
|
||||
EncodeSopp(0x05, 3), // second inner exit -> tail B
|
||||
EncodeSopp(0x02, 0xfffbu), // inner backedge
|
||||
EncodeSMovB32(3, 129), // tail A
|
||||
EncodeSopp(0x02, 2), // tail A -> outer continue
|
||||
EncodeSMovB32(4, 129), // tail B
|
||||
EncodeSopp(0x02, 0), // tail B -> outer continue
|
||||
EncodeSop2(0x00, 0, 0, 129), // outer continue: s_add_u32 s0, s0, 1
|
||||
EncodeSopp(0x02, 0xfff3u), // outer backedge
|
||||
0xbf810000u,
|
||||
};
|
||||
|
||||
ShaderRecompiler::Decoder::Program program;
|
||||
std::string error;
|
||||
Check(ShaderRecompiler::Decoder::DecodeProgram(std::span {shader}, program, &error),
|
||||
error.c_str());
|
||||
|
||||
ShaderRecompiler::CFG::Graph graph;
|
||||
Check(ShaderRecompiler::CFG::BuildGraph(program, graph, &error), error.c_str());
|
||||
const auto original_block_count = graph.blocks.size();
|
||||
Check(ShaderRecompiler::CFG::Structurize(graph, &error), error.c_str());
|
||||
Check(graph.blocks.size() > original_block_count,
|
||||
"nested loop exit tails did not create a private inner merge");
|
||||
|
||||
const auto* outer_header = graph.FindBlockByPc(0);
|
||||
const auto* inner_header = graph.FindBlockByPc(8);
|
||||
Check(outer_header != nullptr && inner_header != nullptr &&
|
||||
outer_header->terminator.loop_header && inner_header->terminator.loop_header,
|
||||
"nested loop exit-tail fixture did not retain both loop headers");
|
||||
Check(inner_header->terminator.merge_block != outer_header->terminator.continue_block,
|
||||
"inner loop merge still aliases the outer continue target");
|
||||
const auto* inner_merge = graph.FindBlock(inner_header->terminator.merge_block);
|
||||
Check(inner_merge != nullptr && inner_merge->inst_begin == inner_merge->inst_end &&
|
||||
inner_merge->terminator.kind == ShaderRecompiler::CFG::TerminatorKind::Branch &&
|
||||
inner_merge->terminator.true_block == outer_header->terminator.continue_block,
|
||||
"private inner merge does not forward to the outer continue target");
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerCfgMixedContinueNonmergeExitDispatcher() {
|
||||
const uint32_t shader[] = {
|
||||
EncodeSopc(0x06, 7, 7), // entry branch bypasses loop -> exit X
|
||||
EncodeSopp(0x05, 5), // entry -> X
|
||||
EncodeSopc(0x0a, 0, 129), // loop: s_cmp_lt_u32 s0, 1
|
||||
EncodeSopp(0x04, 5), // loop exit -> Y
|
||||
EncodeSopc(0x06, 1, 1), // inner condition
|
||||
EncodeSopp(0x05, 1), // nonmerge exit -> X, else continue
|
||||
EncodeSopp(0x02, 0xfffbu), // loop backedge
|
||||
EncodeSMovB32(2, 129), // X
|
||||
EncodeSopp(0x02, 2), // X -> end
|
||||
EncodeSMovB32(3, 129), // Y
|
||||
EncodeSopp(0x02, 0), // Y -> end
|
||||
0xbf810000u,
|
||||
};
|
||||
|
||||
ShaderRecompiler::CompileOptions options;
|
||||
options.stage = ShaderType::Compute;
|
||||
options.dump_ir = true;
|
||||
|
||||
ShaderRecompiler::CompileResult result;
|
||||
std::string error;
|
||||
Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
|
||||
Check(Common::ContainsStr(result.ir_dump, "mode=dispatcher"),
|
||||
"mixed continue/nonmerge exit did not select dispatcher fallback");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 251) != 0,
|
||||
"mixed continue/nonmerge exit dispatcher SPIR-V lacks OpSwitch");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerCfgConditionalLatchNoSelection() {
|
||||
const uint32_t shader[] = {
|
||||
EncodeSopp(0x02, 0), // loop header -> conditional block
|
||||
EncodeSopc(0x06, 0, 0), // s_cmp_eq_u32 s0, s0
|
||||
EncodeSopp(0x05, 1), // loop exit -> end
|
||||
EncodeSopp(0x02, 0xfffcu), // separate latch -> loop header
|
||||
0xbf810000u,
|
||||
};
|
||||
|
||||
ShaderRecompiler::CompileOptions options;
|
||||
options.stage = ShaderType::Compute;
|
||||
options.dump_ir = true;
|
||||
|
||||
ShaderRecompiler::CompileResult result;
|
||||
std::string error;
|
||||
Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
|
||||
Check(Common::ContainsStr(result.ir_dump, "mode=structured"),
|
||||
"conditional latch did not stay on structured path");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 246) != 0,
|
||||
"conditional latch SPIR-V lacks OpLoopMerge");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 247) == 0,
|
||||
"conditional latch SPIR-V unexpectedly used OpSelectionMerge");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 251) == 0,
|
||||
"conditional latch unexpectedly used dispatcher OpSwitch");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerCfgDirectConditionalLatchNoSelection() {
|
||||
const uint32_t shader[] = {
|
||||
EncodeSopp(0x02, 0), // loop header -> conditional latch
|
||||
EncodeSopc(0x06, 0, 0), // s_cmp_eq_u32 s0, s0
|
||||
EncodeSopp(0x05, 0xfffdu), // direct latch backedge -> loop header
|
||||
0xbf810000u,
|
||||
};
|
||||
|
||||
ShaderRecompiler::CompileOptions options;
|
||||
options.stage = ShaderType::Compute;
|
||||
options.dump_ir = true;
|
||||
|
||||
ShaderRecompiler::CompileResult result;
|
||||
std::string error;
|
||||
Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
|
||||
Check(Common::ContainsStr(result.ir_dump, "mode=structured"),
|
||||
"direct conditional latch did not stay on structured path");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 246) != 0,
|
||||
"direct conditional latch SPIR-V lacks OpLoopMerge");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 247) == 0,
|
||||
"direct conditional latch SPIR-V unexpectedly used OpSelectionMerge");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 251) == 0,
|
||||
"direct conditional latch unexpectedly used dispatcher OpSwitch");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerCfgLoopEarlyContinuesNoSelection() {
|
||||
const uint32_t shader[] = {
|
||||
EncodeSMovB32(0, 128), // s0 = 0
|
||||
EncodeSopc(0x0a, 0, 130), // loop: s_cmp_lt_u32 s0, 2
|
||||
@@ -5419,15 +5636,111 @@ void TestNewShaderRecompilerCfgLoopSharedContinueSelectionMerges() {
|
||||
std::string error;
|
||||
Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
|
||||
Check(Common::ContainsStr(result.ir_dump, "mode=structured"),
|
||||
"shared loop continue selections should stay on structured path");
|
||||
Check(!Common::ContainsStr(result.ir_dump, "duplicate structured merge block"),
|
||||
"shared loop continue selections were not split before structurization");
|
||||
Check(SpirvContainsOpcode(result.spirv, 246),
|
||||
"shared loop continue selections SPIR-V lacks OpLoopMerge");
|
||||
Check(SpirvContainsOpcode(result.spirv, 247),
|
||||
"shared loop continue selections SPIR-V lacks OpSelectionMerge");
|
||||
Check(!SpirvContainsOpcode(result.spirv, 251),
|
||||
"shared loop continue selections unexpectedly used dispatcher OpSwitch");
|
||||
"loop early continues should stay on structured path");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 246) != 0,
|
||||
"loop early continues SPIR-V lacks OpLoopMerge");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 247) == 0,
|
||||
"loop early continues SPIR-V unexpectedly used OpSelectionMerge");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 251) == 0,
|
||||
"loop early continues unexpectedly used dispatcher OpSwitch");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerCfgConditionalLoopHeaderSelection() {
|
||||
const uint32_t shader[] = {
|
||||
EncodeSopc(0x06, 0, 0), // loop body selection condition
|
||||
EncodeSopp(0x05, 2), // select path B
|
||||
EncodeSMovB32(1, 129), // path A
|
||||
EncodeSopp(0x02, 1), // path A -> join
|
||||
EncodeSMovB32(2, 129), // path B
|
||||
EncodeSMovB32(3, 129), // join
|
||||
EncodeSopc(0x06, 4, 4), // repeat condition
|
||||
EncodeSopp(0x05, 0xfff8u), // repeat -> guest header
|
||||
0xbf810000u,
|
||||
};
|
||||
|
||||
ShaderRecompiler::Decoder::Program decoded;
|
||||
std::string error;
|
||||
Check(ShaderRecompiler::Decoder::DecodeProgram(std::span {shader}, decoded, &error),
|
||||
error.c_str());
|
||||
ShaderRecompiler::CFG::Graph graph;
|
||||
Check(ShaderRecompiler::CFG::BuildGraph(decoded, graph, &error), error.c_str());
|
||||
const auto original_block_count = graph.blocks.size();
|
||||
Check(ShaderRecompiler::CFG::Structurize(graph, &error), error.c_str());
|
||||
Check(graph.blocks.size() > original_block_count,
|
||||
"conditional guest loop header did not create a synthetic header");
|
||||
|
||||
uint32_t loop_headers = 0;
|
||||
uint32_t selection_headers = 0;
|
||||
for (const auto& block: graph.blocks) {
|
||||
if (block.terminator.loop_header) {
|
||||
loop_headers++;
|
||||
Check(block.inst_begin == block.inst_end &&
|
||||
block.terminator.kind == ShaderRecompiler::CFG::TerminatorKind::Branch,
|
||||
"canonical loop header is not an empty unconditional block");
|
||||
} else if (block.terminator.kind ==
|
||||
ShaderRecompiler::CFG::TerminatorKind::ConditionalBranch &&
|
||||
block.terminator.merge_block != UINT32_MAX) {
|
||||
selection_headers++;
|
||||
}
|
||||
}
|
||||
Check(loop_headers == 1u && selection_headers == 1u,
|
||||
"guest conditional was not separated from the loop header");
|
||||
|
||||
ShaderRecompiler::CompileOptions options;
|
||||
options.stage = ShaderType::Compute;
|
||||
ShaderRecompiler::CompileResult result;
|
||||
Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 246) == 1u,
|
||||
"conditional loop-header SPIR-V has the wrong loop-merge count");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 247) == 1u,
|
||||
"conditional loop-header SPIR-V has the wrong selection-merge count");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 251) == 0u,
|
||||
"conditional loop-header unexpectedly used dispatcher OpSwitch");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerCfgMultipleLoopLatches() {
|
||||
const uint32_t shader[] = {
|
||||
EncodeSopc(0x0a, 0, 129), // loop condition
|
||||
EncodeSopp(0x04, 5), // loop exit -> end
|
||||
EncodeSopc(0x06, 1, 1), // early repeat condition
|
||||
EncodeSopp(0x05, 0xfffcu), // early repeat -> header
|
||||
EncodeSMovB32(2, 129), // body
|
||||
EncodeSMovB32(3, 129), // body tail
|
||||
EncodeSopp(0x02, 0xfff9u), // ordinary latch -> header
|
||||
0xbf810000u,
|
||||
};
|
||||
|
||||
ShaderRecompiler::Decoder::Program decoded;
|
||||
std::string error;
|
||||
Check(ShaderRecompiler::Decoder::DecodeProgram(std::span {shader}, decoded, &error),
|
||||
error.c_str());
|
||||
ShaderRecompiler::CFG::Graph graph;
|
||||
Check(ShaderRecompiler::CFG::BuildGraph(decoded, graph, &error), error.c_str());
|
||||
const auto original_block_count = graph.blocks.size();
|
||||
Check(graph.back_edges.size() == 2u, "multiple-latch fixture lacks two native backedges");
|
||||
Check(ShaderRecompiler::CFG::Structurize(graph, &error), error.c_str());
|
||||
Check(graph.blocks.size() == original_block_count + 1u,
|
||||
"multiple native latches did not create one synthetic continue");
|
||||
Check(graph.back_edges.size() == 1u && graph.natural_loops.size() == 1u,
|
||||
"multiple native latches were not coalesced to one SPIR-V backedge");
|
||||
const auto& loop = graph.natural_loops.front();
|
||||
const auto* continue_block = graph.FindBlock(loop.continue_block);
|
||||
Check(continue_block != nullptr && continue_block->inst_begin == continue_block->inst_end &&
|
||||
continue_block->predecessors.size() == 2u,
|
||||
"canonical continue does not join both native latches");
|
||||
|
||||
ShaderRecompiler::CompileOptions options;
|
||||
options.stage = ShaderType::Compute;
|
||||
ShaderRecompiler::CompileResult result;
|
||||
Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 246) == 1u,
|
||||
"multiple-latch SPIR-V has the wrong loop-merge count");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 247) == 0u,
|
||||
"multiple-latch SPIR-V unexpectedly used a selection merge");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 251) == 0u,
|
||||
"multiple-latch SPIR-V unexpectedly used dispatcher OpSwitch");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
@@ -5457,6 +5770,85 @@ void TestNewShaderRecompilerCfgDuplicateMergeStructuredSplit() {
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerCfgOverlappingEarlyExitLadder() {
|
||||
const uint32_t shader[] = {
|
||||
EncodeSopc(0x06, 0, 0), // block 0
|
||||
EncodeSopp(0x04, 2), // block 0 -> 2 or 1
|
||||
EncodeSopc(0x06, 1, 1), // block 1
|
||||
EncodeSopp(0x04, 6), // block 1 -> 5 or 2
|
||||
EncodeSopc(0x06, 2, 2), // block 2
|
||||
EncodeSopp(0x04, 4), // block 2 -> 5 or 3
|
||||
EncodeSopc(0x06, 3, 3), // block 3
|
||||
EncodeSopp(0x04, 2), // block 3 -> 5 or 4
|
||||
EncodeSMovB32(4, 129), // block 4
|
||||
0xbf810000u, // block 4 -> 6
|
||||
EncodeSMovB32(5, 129), // block 5
|
||||
0xbf810000u, // block 5 -> 6
|
||||
};
|
||||
|
||||
ShaderRecompiler::Decoder::Program decoded;
|
||||
std::string error;
|
||||
Check(ShaderRecompiler::Decoder::DecodeProgram(std::span {shader}, decoded, &error),
|
||||
error.c_str());
|
||||
ShaderRecompiler::CFG::Graph graph;
|
||||
Check(ShaderRecompiler::CFG::BuildGraph(decoded, graph, &error), error.c_str());
|
||||
Check(graph.blocks.size() == 7u && graph.blocks[0].successors == std::vector<uint32_t>({1, 2}) &&
|
||||
graph.blocks[0].terminator.true_block == 2u &&
|
||||
graph.blocks[0].terminator.false_block == 1u &&
|
||||
graph.blocks[1].successors == std::vector<uint32_t>({2, 5}) &&
|
||||
graph.blocks[1].terminator.true_block == 5u &&
|
||||
graph.blocks[1].terminator.false_block == 2u &&
|
||||
graph.blocks[2].successors == std::vector<uint32_t>({3, 5}) &&
|
||||
graph.blocks[2].terminator.true_block == 5u &&
|
||||
graph.blocks[2].terminator.false_block == 3u &&
|
||||
graph.blocks[3].successors == std::vector<uint32_t>({4, 5}) &&
|
||||
graph.blocks[3].terminator.true_block == 5u &&
|
||||
graph.blocks[3].terminator.false_block == 4u &&
|
||||
graph.blocks[4].successors == std::vector<uint32_t>({6}) &&
|
||||
graph.blocks[5].successors == std::vector<uint32_t>({6}),
|
||||
"overlapping early-exit fixture does not match the observed shader CFG");
|
||||
Check(ShaderRecompiler::CFG::Structurize(graph, &error), error.c_str());
|
||||
std::vector<bool> reachable(graph.blocks.size());
|
||||
std::vector<uint32_t> pending = {graph.entry_block};
|
||||
while (!pending.empty()) {
|
||||
const auto block_id = pending.back();
|
||||
pending.pop_back();
|
||||
if (reachable[block_id]) {
|
||||
continue;
|
||||
}
|
||||
reachable[block_id] = true;
|
||||
pending.insert(pending.end(), graph.blocks[block_id].successors.begin(),
|
||||
graph.blocks[block_id].successors.end());
|
||||
}
|
||||
Check(std::all_of(reachable.begin(), reachable.end(), [](bool value) { return value; }),
|
||||
"overlapping early-exit structurization left unreachable blocks");
|
||||
std::vector<uint32_t> merges;
|
||||
for (const auto& block: graph.blocks) {
|
||||
if (block.terminator.kind == ShaderRecompiler::CFG::TerminatorKind::ConditionalBranch) {
|
||||
Check(block.terminator.merge_block != UINT32_MAX &&
|
||||
std::find(merges.begin(), merges.end(), block.terminator.merge_block) ==
|
||||
merges.end(),
|
||||
"overlapping early-exit structurization retained a shared merge");
|
||||
merges.push_back(block.terminator.merge_block);
|
||||
}
|
||||
}
|
||||
|
||||
ShaderRecompiler::CompileOptions options;
|
||||
options.stage = ShaderType::Pixel;
|
||||
options.dump_ir = true;
|
||||
ShaderRecompiler::CompileResult result;
|
||||
Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
|
||||
Check(Common::ContainsStr(result.ir_dump, "mode=structured"),
|
||||
"overlapping early-exit ladder did not stay on the structured path");
|
||||
Check(!Common::ContainsStr(result.ir_dump, "duplicate structured merge block"),
|
||||
"overlapping early-exit ladder retained a shared merge");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 247) >= 4u,
|
||||
"overlapping early-exit ladder lost its selections");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 251) == 0u,
|
||||
"overlapping early-exit ladder used dispatcher OpSwitch");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerCfgIrreducibleDispatcher() {
|
||||
const uint32_t shader[] = {
|
||||
EncodeSopp(0x05, 2), // entry -> B, fallthrough A
|
||||
@@ -7014,7 +7406,6 @@ int main() {
|
||||
using namespace Libs::Graphics;
|
||||
|
||||
EnsureConfigInitialized();
|
||||
|
||||
TestResourceDescriptorClassification();
|
||||
TestNativeShaderResourceDependencies();
|
||||
TestNormalizedImageContracts();
|
||||
@@ -7086,8 +7477,18 @@ int main() {
|
||||
TestNewShaderRecompilerCfgLoopHeaderBufferLoadDispatcher();
|
||||
TestNewShaderRecompilerCfgLoopHeaderDsAppendConsumeDispatcher();
|
||||
TestNewShaderRecompilerCfgSharedOuterAndLoopMerge();
|
||||
TestNewShaderRecompilerCfgLoopSharedContinueSelectionMerges();
|
||||
TestNewShaderRecompilerCfgLoopEarlyBreakNoSelection();
|
||||
TestNewShaderRecompilerCfgNestedLoopNonlocalExitDispatcher();
|
||||
TestNewShaderRecompilerCfgNestedLoopLocalExitNoSelection();
|
||||
TestNewShaderRecompilerCfgNestedLoopExitTailMergeSplit();
|
||||
TestNewShaderRecompilerCfgMixedContinueNonmergeExitDispatcher();
|
||||
TestNewShaderRecompilerCfgConditionalLatchNoSelection();
|
||||
TestNewShaderRecompilerCfgDirectConditionalLatchNoSelection();
|
||||
TestNewShaderRecompilerCfgLoopEarlyContinuesNoSelection();
|
||||
TestNewShaderRecompilerCfgConditionalLoopHeaderSelection();
|
||||
TestNewShaderRecompilerCfgMultipleLoopLatches();
|
||||
TestNewShaderRecompilerCfgDuplicateMergeStructuredSplit();
|
||||
TestNewShaderRecompilerCfgOverlappingEarlyExitLadder();
|
||||
TestNewShaderRecompilerCfgIrreducibleDispatcher();
|
||||
TestNewShaderRecompilerExecMaskHelpers();
|
||||
TestComputeShaderInputWaveSize();
|
||||
|
||||
Reference in New Issue
Block a user