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

Use C++20 features to create opcode tables at compile time #11400

Merged
merged 6 commits into from Mar 17, 2023

Conversation

Pokechu22
Copy link
Contributor

@Pokechu22 Pokechu22 commented Jan 2, 2023

Previously, Interpreter_Tables.cpp contained both the information for running an instruction in the interpreter AND the flags used for that instruction everywhere (including the JITs). Furthermore, this couldn't be const, as the last 3 members of GekkoOPInfo were mutable (for statistics purposes) (and also, those 3 members were explicitly set to 0 on all instructions, even though that wasn't useful information)).

Using C++20 features (consteval as well as constexpr std::array::fill (Android still doesn't support this)) these are now created at compile time, and also verify that there are no duplicate entries. The stats portion of GekkoOPInfo is now handled by a pointer to a separate array which allows the rest of it to be const.

@Pokechu22 Pokechu22 force-pushed the better-ppc-tables branch 4 times, most recently from 8f14b94 to fe24183 Compare January 2, 2023 02:40
@dvessel
Copy link
Contributor

dvessel commented Jan 2, 2023

M1, Crashes when starting a game. Error & backtrace:

33:02:024 Core/PowerPC/Interpreter/Interpreter.cpp:320 E[PowerPC]: Warning: An error occurred.


IntCPU: Unknown instruction 4e800020 at PC = 8120028c  last_PC = 81200288  LR = 00000000


  Condition: 0
  File: /Users/foo/Projects/mac/dolphin/Source/Core/Core/PowerPC/Interpreter/Interpreter.cpp
  Line: 320
  Function: unknown_instruction

Ignore and continue?
Process 92639 stopped
* thread #28, name = 'Emuthread - Starting', stop reason = EXC_BREAKPOINT (code=1, subcode=0x100aef4ac)
    frame #0: 0x0000000100aef4ac Dolphin`Interpreter::unknown_instruction(UGeckoInstruction) + 468
Dolphin`Interpreter::unknown_instruction:
->  0x100aef4ac <+468>: brk    #0x1
    0x100aef4b0 <+472>: b      0x100aef4b4               ; <+476>
    0x100aef4b4 <+476>: b      0x100aef4b8               ; <+480>
    0x100aef4b8 <+480>: bl     0x100042ef4               ; Core::System::GetInstance()
Target 0: (Dolphin) stopped.
(lldb) bt
* thread #28, name = 'Emuthread - Starting', stop reason = EXC_BREAKPOINT (code=1, subcode=0x100aef4ac)
  * frame #0: 0x0000000100aef4ac Dolphin`Interpreter::unknown_instruction(UGeckoInstruction) + 468
    frame #1: 0x0000000100aee80c Dolphin`Interpreter::RunTable19(UGeckoInstruction) + 64
    frame #2: 0x0000000100aee95c Dolphin`Interpreter::RunInterpreterOp(UGeckoInstruction) + 52
    frame #3: 0x0000000100aeed8c Dolphin`Interpreter::SingleStepInner() + 220
    frame #4: 0x0000000100aeec30 Dolphin`Interpreter::SingleStep() + 64
    frame #5: 0x0000000100b296c0 Dolphin`PowerPC::SingleStep() + 32
    frame #6: 0x00000001003f4ee8 Dolphin`CBoot::RunFunction(unsigned int) + 64
    frame #7: 0x00000001003f59dc Dolphin`CBoot::RunApploader(bool, DiscIO::VolumeDisc const&, std::__1::vector<DiscIO::Riivolution::Patch, std::__1::allocator<DiscIO::Riivolution::Patch> > const&) + 440
    frame #8: 0x00000001003f6db4 Dolphin`CBoot::EmulatedBS2_GC(Core::System&, DiscIO::VolumeDisc const&, std::__1::vector<DiscIO::Riivolution::Patch, std::__1::allocator<DiscIO::Riivolution::Patch> > const&) + 456
    frame #9: 0x00000001003ffd98 Dolphin`CBoot::EmulatedBS2(Core::System&, bool, DiscIO::VolumeDisc const&, std::__1::vector<DiscIO::Riivolution::Patch, std::__1::allocator<DiscIO::Riivolution::Patch> > const&) + 72
    frame #10: 0x0000000100419cb0 Dolphin`CBoot::BootUp(Core::System&, std::__1::unique_ptr<BootParameters, std::__1::default_delete<BootParameters> >)::BootTitle::operator()(BootParameters::Disc&) const + 184
    frame #11: 0x0000000100419be8 Dolphin`decltype(static_cast<CBoot::BootUp(Core::System&, std::__1::unique_ptr<BootParameters, std::__1::default_delete<BootParameters> >)::BootTitle>(fp)(static_cast<BootParameters::Disc&>(fp0))) std::__1::__invoke_constexpr<CBoot::BootUp(Core::System&, std::__1::unique_ptr<BootParameters, std::__1::default_delete<BootParameters> >)::BootTitle, BootParameters::Disc&>(CBoot::BootUp(Core::System&, std::__1::unique_ptr<BootParameters, std::__1::default_delete<BootParameters> >)::BootTitle&&, BootParameters::Disc&) + 32
    frame #12: 0x0000000100419b9c Dolphin`decltype(auto) std::__1::__variant_detail::__visitation::__variant::__value_visitor<CBoot::BootUp(Core::System&, std::__1::unique_ptr<BootParameters, std::__1::default_delete<BootParameters> >)::BootTitle>::operator()<std::__1::__variant_detail::__alt<0ul, BootParameters::Disc>&>(std::__1::__variant_detail::__alt<0ul, BootParameters::Disc>&) const + 64
    frame #13: 0x0000000100419b4c Dolphin`decltype(static_cast<std::__1::__variant_detail::__visitation::__variant::__value_visitor<CBoot::BootUp(Core::System&, std::__1::unique_ptr<BootParameters, std::__1::default_delete<BootParameters> >)::BootTitle>>(fp)(static_cast<std::__1::__variant_detail::__alt<0ul, BootParameters::Disc>&>(fp0))) std::__1::__invoke_constexpr<std::__1::__variant_detail::__visitation::__variant::__value_visitor<CBoot::BootUp(Core::System&, std::__1::unique_ptr<BootParameters, std::__1::default_delete<BootParameters> >)::BootTitle>, std::__1::__variant_detail::__alt<0ul, BootParameters::Disc>&>(std::__1::__variant_detail::__visitation::__variant::__value_visitor<CBoot::BootUp(Core::System&, std::__1::unique_ptr<BootParameters, std::__1::default_delete<BootParameters> >)::BootTitle>&&, std::__1::__variant_detail::__alt<0ul, BootParameters::Disc>&) + 32
    frame #14: 0x00000001004183c0 Dolphin`decltype(auto) std::__1::__variant_detail::__visitation::__base::__dispatcher<0ul>::__dispatch<std::__1::__variant_detail::__visitation::__variant::__value_visitor<CBoot::BootUp(Core::System&, std::__1::unique_ptr<BootParameters, std::__1::default_delete<BootParameters> >)::BootTitle>&&, std::__1::__variant_detail::__base<(std::__1::__variant_detail::_Trait)1, BootParameters::Disc, BootParameters::Executable, DiscIO::VolumeWAD, BootParameters::NANDTitle, BootParameters::IPL, BootParameters::DFF>&>(std::__1::__variant_detail::__visitation::__variant::__value_visitor<CBoot::BootUp(Core::System&, std::__1::unique_ptr<BootParameters, std::__1::default_delete<BootParameters> >)::BootTitle>&&, std::__1::__variant_detail::__base<(std::__1::__variant_detail::_Trait)1, BootParameters::Disc, BootParameters::Executable, DiscIO::VolumeWAD, BootParameters::NANDTitle, BootParameters::IPL, BootParameters::DFF>&) + 48
    frame #15: 0x0000000100418314 Dolphin`decltype(auto) std::__1::__variant_detail::__visitation::__base::__visit_alt<std::__1::__variant_detail::__visitation::__variant::__value_visitor<CBoot::BootUp(Core::System&, std::__1::unique_ptr<BootParameters, std::__1::default_delete<BootParameters> >)::BootTitle>, std::__1::__variant_detail::__impl<BootParameters::Disc, BootParameters::Executable, DiscIO::VolumeWAD, BootParameters::NANDTitle, BootParameters::IPL, BootParameters::DFF>&>(std::__1::__variant_detail::__visitation::__variant::__value_visitor<CBoot::BootUp(Core::System&, std::__1::unique_ptr<BootParameters, std::__1::default_delete<BootParameters> >)::BootTitle>&&, std::__1::__variant_detail::__impl<BootParameters::Disc, BootParameters::Executable, DiscIO::VolumeWAD, BootParameters::NANDTitle, BootParameters::IPL, BootParameters::DFF>&) + 104
    frame #16: 0x0000000100418270 Dolphin`decltype(auto) std::__1::__variant_detail::__visitation::__variant::__visit_alt<std::__1::__variant_detail::__visitation::__variant::__value_visitor<CBoot::BootUp(Core::System&, std::__1::unique_ptr<BootParameters, std::__1::default_delete<BootParameters> >)::BootTitle>, std::__1::variant<BootParameters::Disc, BootParameters::Executable, DiscIO::VolumeWAD, BootParameters::NANDTitle, BootParameters::IPL, BootParameters::DFF>&>(std::__1::__variant_detail::__visitation::__variant::__value_visitor<CBoot::BootUp(Core::System&, std::__1::unique_ptr<BootParameters, std::__1::default_delete<BootParameters> >)::BootTitle>&&, std::__1::variant<BootParameters::Disc, BootParameters::Executable, DiscIO::VolumeWAD, BootParameters::NANDTitle, BootParameters::IPL, BootParameters::DFF>&) + 60
    frame #17: 0x00000001004181f8 Dolphin`decltype(auto) std::__1::__variant_detail::__visitation::__variant::__visit_value<CBoot::BootUp(Core::System&, std::__1::unique_ptr<BootParameters, std::__1::default_delete<BootParameters> >)::BootTitle, std::__1::variant<BootParameters::Disc, BootParameters::Executable, DiscIO::VolumeWAD, BootParameters::NANDTitle, BootParameters::IPL, BootParameters::DFF>&>(CBoot::BootUp(Core::System&, std::__1::unique_ptr<BootParameters, std::__1::default_delete<BootParameters> >)::BootTitle&&, std::__1::variant<BootParameters::Disc, BootParameters::Executable, DiscIO::VolumeWAD, BootParameters::NANDTitle, BootParameters::IPL, BootParameters::DFF>&) + 56
    frame #18: 0x000000010041815c Dolphin`decltype(auto) std::__1::visit<CBoot::BootUp(Core::System&, std::__1::unique_ptr<BootParameters, std::__1::default_delete<BootParameters> >)::BootTitle, std::__1::variant<BootParameters::Disc, BootParameters::Executable, DiscIO::VolumeWAD, BootParameters::NANDTitle, BootParameters::IPL, BootParameters::DFF>&, void>(CBoot::BootUp(Core::System&, std::__1::unique_ptr<BootParameters, std::__1::default_delete<BootParameters> >)::BootTitle&&, std::__1::variant<BootParameters::Disc, BootParameters::Executable, DiscIO::VolumeWAD, BootParameters::NANDTitle, BootParameters::IPL, BootParameters::DFF>&) + 68
    frame #19: 0x000000010041808c Dolphin`CBoot::BootUp(Core::System&, std::__1::unique_ptr<BootParameters, std::__1::default_delete<BootParameters> >) + 164
    frame #20: 0x00000001005ce064 Dolphin`Core::EmuThread(std::__1::unique_ptr<BootParameters, std::__1::default_delete<BootParameters> >, WindowSystemInfo) + 1012
    frame #21: 0x00000001005cee44 Dolphin`decltype(static_cast<void (*>(fp)(static_cast<std::__1::unique_ptr<BootParameters, std::__1::default_delete<BootParameters> >>(fp0), static_cast<WindowSystemInfo>(fp0))) std::__1::__invoke<void (*)(std::__1::unique_ptr<BootParameters, std::__1::default_delete<BootParameters> >, WindowSystemInfo), std::__1::unique_ptr<BootParameters, std::__1::default_delete<BootParameters> >, WindowSystemInfo>(void (*&&)(std::__1::unique_ptr<BootParameters, std::__1::default_delete<BootParameters> >, WindowSystemInfo), std::__1::unique_ptr<BootParameters, std::__1::default_delete<BootParameters> >&&, WindowSystemInfo&&) + 76
    frame #22: 0x00000001005ced48 Dolphin`void std::__1::__thread_execute<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, void (*)(std::__1::unique_ptr<BootParameters, std::__1::default_delete<BootParameters> >, WindowSystemInfo), std::__1::unique_ptr<BootParameters, std::__1::default_delete<BootParameters> >, WindowSystemInfo, 2ul, 3ul>(std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, void (*)(std::__1::unique_ptr<BootParameters, std::__1::default_delete<BootParameters> >, WindowSystemInfo), std::__1::unique_ptr<BootParameters, std::__1::default_delete<BootParameters> >, WindowSystemInfo>&, std::__1::__tuple_indices<2ul, 3ul>) + 80
    frame #23: 0x00000001005ce7f0 Dolphin`void* std::__1::__thread_proxy<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, void (*)(std::__1::unique_ptr<BootParameters, std::__1::default_delete<BootParameters> >, WindowSystemInfo), std::__1::unique_ptr<BootParameters, std::__1::default_delete<BootParameters> >, WindowSystemInfo> >(void*) + 80
    frame #24: 0x000000018e62506c libsystem_pthread.dylib`_pthread_start + 148

@Pokechu22
Copy link
Contributor Author

Also affects x86-64 windows. I guess I didn't test enough after one of my rebases an broke something.

@Pokechu22 Pokechu22 marked this pull request as draft January 2, 2023 19:04
void Interpreter::RunTable4(UGeckoInstruction inst)
{
s_interpreter_op_table4[inst.SUBOP10](inst);
}
void Interpreter::RunTable19(UGeckoInstruction inst)
{
s_interpreter_op_table4[inst.SUBOP10](inst);
}
void Interpreter::RunTable31(UGeckoInstruction inst)
{
s_interpreter_op_table4[inst.SUBOP10](inst);
}
void Interpreter::RunTable59(UGeckoInstruction inst)
{
s_interpreter_op_table4[inst.SUBOP5](inst);
}
void Interpreter::RunTable63(UGeckoInstruction inst)
{
s_interpreter_op_table4[inst.SUBOP10](inst);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's the source of the problem; I use table4 for all of these by accident. The JITs are fine it seems. Although I'm not sure why this would be getting ran on the JITs; the whole splitting should mean that the JIT doesn't need to look at the interpreter at all now unless it's actually falling back.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While the JIT doesn't run instructions that require a fallback all that often, there is a non-trivial number of instructions that require a fallback, so it sounds plausible to me that you would run into some of them at least once when playing a game.

Also, dvessel's stack trace was from while the apploader was running. We always use the interpreter for that.

Interpreter::Instruction Interpreter::GetInterpreterOp(UGeckoInstruction inst)
{
// Check for the appropriate subtable ahead of time.
// (This is used by the cached interpreter and JIT, and called once per instruction, so spending a
// bit of extra time to optimise is worthwhile)
Interpreter::Instruction result = s_interpreter_op_table[inst.OPCD];
if (result == Interpreter::RunTable4)
return s_interpreter_op_table4[inst.SUBOP10];
else if (result == Interpreter::RunTable19)
return s_interpreter_op_table19[inst.SUBOP10];
else if (result == Interpreter::RunTable31)
return s_interpreter_op_table31[inst.SUBOP10];
else if (result == Interpreter::RunTable59)
return s_interpreter_op_table59[inst.SUBOP5];
else if (result == Interpreter::RunTable63)
return s_interpreter_op_table63[inst.SUBOP10];
else
return result;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... and a second, much more insidious thing also happens: because RunTable4, RunTable19, RunTable31, and RunTable63 all had the same body (RunTable59 used SUBOP5 so it was still different), the compiler is allowed to treat them as the same function, and then they end up having the same address, so this also ended up doing the wrong thing. However, the compiler is not required to treat them as the same function, so this only happened on release builds, not debug builds.

On x86-64, GetInterpreterOp was getting called from a 7c1083a6 mtspr IBAT0U, r0 instruction (and the JIT falls back to the interpreter for all but a few common mtspr instructions). So that's how this was getting called.

The instruction you had is a blr, which doesn't have the same fallback behavior, but because we don't use the JIT while running the apploader (compare #10923) the same previously mentioned issue with the RunTable functions applied (RunInterpreterOp would be used in that path, not GetInterpreterOp).

@Pokechu22 Pokechu22 force-pushed the better-ppc-tables branch 2 times, most recently from efc327d to 2ebf80c Compare January 2, 2023 21:01
@Pokechu22 Pokechu22 marked this pull request as ready for review January 3, 2023 07:56
@dvessel
Copy link
Contributor

dvessel commented Jan 3, 2023

No longer crashing. Looks good~!

@shuffle2
Copy link
Contributor

shuffle2 commented Jan 6, 2023

just curious: for constexpr std::array::fill on android is that limitation of their current tools, or are we behind/misconfiguring the toolchain somehow (i.e. can we enable it somehow or do we have to wait)?

@Pokechu22
Copy link
Contributor Author

I assume it's a limitation of their tools, as other C++20 features do work properly for the most part (std::bit_cast is another exception; see #10957).

@shuffle2
Copy link
Contributor

shuffle2 commented Jan 6, 2023

It seems constexpr std::array::fill and std::bit_cast should be supported by clang libc++ 14 or later.
Android ndk 25 (the latest + what we're using) uses this version: https://android.googlesource.com/platform/prebuilts/clang/host/windows-x86/+/refs/heads/master/clang-r450784d/clang_source_info.md
From january 2022.

It looks like they just picked a random pre-release commit from before 14.0 was actually released :(

https://github.com/android/ndk/wiki#release-schedule and the next version of ndk is not expected to be released until end of 2023....really, not great. (however, they say non-LTS builds are released quarterly, so maybe there is hope for updating to one of those beforehand?)

@shuffle2
Copy link
Contributor

shuffle2 commented Mar 4, 2023

fwiw, looks like NDK released a new version, r25c but it's still based on same old clang :(

Copy link
Contributor

@AdmiralCurtiss AdmiralCurtiss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't checked all this in detail but this seems fine. I'm probably gonna merge this before I try to de-globalize the Interpreter and JIT.


for (auto& tpl : s_primary_table)
{
ASSERT(table[tpl.opcode] == &Jit64::FallBackToInterpreter);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm kinda surprised our ASSERT macro works in a consteval context, to be honest. But I guess it's fine if it works?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the ASSERT fails, then compilation fails, which is the behavior we want. The message in that case isn't that great, so it might make sense to add some kind of CONSTEVAL_ASSERT macro instead, but I don't think it's that important. (static_assert didn't seem to behave in a useful manner when I tested it, though I don't remember exactly why.)

@AdmiralCurtiss
Copy link
Contributor

This seems to break booting Kirby Air Ride (E) in Interpreter, but not Cached Interpreter weirdly enough. Lemme see if I can figure out why...

@AdmiralCurtiss
Copy link
Contributor

It's 6911b6d, you changed behavior here. m_prev_inst is overwritten after you call PPCTables::GetOpInfo().

const GekkoOPInfo* opinfo = PPCTables::GetOpInfo(m_prev_inst);

if (HandleFunctionHooking(PowerPC::ppcState.pc))
{
UpdatePC();
return PPCTables::GetOpInfo(m_prev_inst)->numCycles;
return opinfo->num_cycles;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, does that mean that this code is incorrect, as it meant that a hooked function uses the cycle count for the previous instruction instead of the current instruction? Not that exact cycle counts mean anything if we're hooking a function.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're right, yeah, at least I don't see any place m_prev_inst would be updated in this case to point at the current instruction. Probably better fixed in another PR though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regression fixed. I've also added a TODO for this code.

This also allows use of constexpr in both places. Some additional work was needed in PPCTables due to mutable data associated with each opcode.
We don't have getters for other flags, so it's not useful to have that.
@AdmiralCurtiss AdmiralCurtiss merged commit 49b495f into dolphin-emu:master Mar 17, 2023
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
5 participants