This improves my PC's performance on RS2 Hoth by... 0.1% or so, which I
think is within the margin of error. But this change also cuts down on
boilerplate.
If the call to `Open` a perf map fails don't set `s_is_enabled` (though
it could already be true if you're also using VTUNE) and don't call
`std::setvbuf` with a null stream.
Also fix a typo in a comment (`if` -> `in`)
If all inputs to an fmadds instruction (including cousins like fmsubs,
fnmadd...) are single-precision, then the result is identical between a
double-precision calculation with an error-free transform (whether the
calculation is fused or not) and a single-precision FMA instruction
(must be fused). So as a performance optimization in JitArm64, if we
were going to use double precision with EFT but the inputs are singles,
instead we'll use a normal single-precision FMA instruction without
anything extra. This lets us skip both the EFT and double-to-single
conversions.
Also renaming `inaccurate_fma` to `nonfused` because it's confusing that
`inaccurate_fma` and `m_accurate_fmadds` have such similar names
despite controlling separate things.
If result_reg is set to a temporary register instead of VD because of
accurate NaNs, there's no need to allocate a secondary temporary
register because of inaccurate FMA.
TextureCacheBase::LoadImpl has a hot path where the passed-in
TextureInfo never gets used. Instead of passing in a TextureInfo, let's
pass in the stage and create the TextureInfo from the stage if needed.
This unlocks somewhere above an additional 4% performance boost in the
Hoth level of Rogue Squadron 2 on my PC. Performance varies, making it
difficult for me to measure, so treat this as a very approximate number.
When we're emulating single-precision FMA using an FMA instruction,
there's no precision benefit from using a double-precision instruction,
assuming all inputs are single-precision. But when we're emulating
single-precision FMA using separate multiplication and addition
instructions, there is.
This change increases the precision of inaccurate FMA to the same level
as Jit64, which matters since the only reason we have the inaccurate
FMA mode is for sync compatibility with Jit64.
The check only happened if `USE_VTUNE` wasn't defined, and in that case
it was immediately followed by the same check again.
The check used to avoid an unnecessary call to `StringFromFormatV` in
certain circumstances, but that call was removed in
be81fe86e1.
Building on macOS with a recent CMake version would result in the
following error:
CMake Error at Source/Core/MacUpdater/CMakeLists.txt:48 (add_custom_command):
The following keywords are not supported when using
add_custom_command(TARGET): DEPENDS
As it turns out, this form of `add_custom_command` does not accept
DEPENDS, but versions of CMake prior to 3.31 silently dropped it.
The TextureInfo constructor creates a vector of MipLevels. This could be
good for performance if MipLevels are accessed very often for each
TextureInfo, but that's not the case. Dolphin creates thousands of
TextureInfos per second that it never accesses the mipmap levels of
because there's a hit in the texture cache, and in the uncommon case of
a texture cache miss, the mipmap levels only get looped through once.
To make the common case of texture cache hits as fast as possible, let's
not create a vector in the TextureInfo constructor. This commit
implements a custom iterator for MipLevels instead.
In my testing on the Death Star level of Rogue Squadron 2, this speeds
up TextureInfo::FromStage by 200%, giving an overall emulation speedup
of a bit over 1%. Results on the Hoth level are even better, with
TextureInfo::FromStage being close to 300% faster and overall emulation
being over 4% faster. (Single core, no GPU texture decoding.)
Profiling Dolphin using Heaptrack, this turns out to be the cause of
more than half of all temporary allocations. Though since it's in a
separate thread, I suppose it wasn't affecting performance very much.
Combined with the previous commit, this brings the TrampolineInfo struct
down to 48 bytes. This matters, because Jit64 has a big
std::unordered_map where it stores many megabytes of TrampolineInfo
entries.
The key saving comes from shrinking the len member from u32 to u16. It
should be safe to even turn it into a u8, but going that far brings no
additional savings due to how the padding works out.
By moving members of the OpArg struct around, we can cut down on how
much padding the struct needs. Now it has a size of 16 bytes, small
enough for function calls to pass it in two registers instead of on the
stack.
Re-added a line of code from a cancelled PR (that I thought was in
main already) that RetroAchievements uses to properly synchronize
memory reads and writes with the emulator frames. This appears to
fix some pretty major freezing and deadlocks in the RA dev tools.
CMake support for c++23 was added in 3.20.
Remove statements explicitly setting the following policies to NEW.
These policies were introduced in or before 3.20, and now that 3.20 is
the minimum version they will automatically have the NEW behavior.
Policy: Introduced in
CMP0079: 3.13
CMP0084: 3.14
CMP0091: 3.15
CMP0092: 3.15
CMP0099: 3.17
CMP0117: 3.20
Disable scanning c++ source files for module imports (introduced as
CMP0155 in 3.28) since we don't use modules and that policy triggers
build errors with Clang if the clang-scan-deps tool isn't installed.
cpp-ipc is explicitely only available on Windows, Linux, QNX, and FreeBSD. Trying to build dolphin on any another BSD such as OpenBSD or Haiku currently leads to failure because of this.
During my quest to rid Dolphin of the memory-unsafe GetPointer function,
I didn't realize that DSPHLE had its own set of memory access functions
that were essentially duplicating the standard ones. This commit gets
rid of them and replaces calls to them with calls to MemoryManager.
Move the constructor and destructor after the definition of the class
`Internal`.
This fixes an error generated by Clang from the destructor of
`std::unique_ptr<Internal>` when setting the standard version to c++23:
`invalid application of 'sizeof' to an incomplete type 'Metal::ObjectCache::Internal'`.
Fix an error generated by Clang from the destructor of
`std::unique_ptr<FileInfo> m_file_info` when setting the standard
version to c++23:
`invalid application of 'sizeof' to an incomplete type 'DiscIO::FileInfo'`
Checked on hardware that this bias was not added because I had assumed the other way around would be true, forgot to ask about making a PR for this when I initially had done so
If we're on an x64 CPU that doesn't have the MOVBE extension, trying to
SwapAndStore a host register results in that register's value getting
clobbered with the swapped value. Jit64::stX and Jit64::stXx detect this
case, and if necessary, emit a MOV to a register that's fine to clobber.
This logic was broken by the merge of PR 12134. Jit64::stX and
Jit64::stXx were assuming that if RegCache::IsImm returns true for a
guest register, calling RegCache::Use or RegCache::BindOrImm for that
guest register would result in an immediate. However, PR 12134 made it
possible for a guest register to have both a host register and an
immediate in the register cache at the same time. When this happens,
RegCache::IsImm returns true, yet RegCache::Use and RegCache::BindForImm
return an RCOpArg whose Location returns a host register. (To make it
extra confusing, RCOpArg::IsImm calls RegCache::IsImm if the RCOpArg
came from RegCache, so RCOpArg::IsImm returns true!)
To fix this, in cases where Jit64::stX and Jit64::stXx explicitly need
an immediate to avoid having to emit an extra MOV, let's call
RegCache::Imm32 so that we're certain that we're getting an immediate.
This fixes an issue on older x64 CPUs that manifested as e.g. completely
broken graphics in Spyro: Enter the Dragonfly.
The constant propagation PR made it so that a guest register can be
present in the register cache as both a host register and an immediate
at the same time. If such a guest register is requested from the
register cache, the register cache prefers returning it as a host
register. However, RCOpArg::IsImm still returns true in this case. This
is confusing, especially since OpArg::IsImm does not return true if the
RCOpArg is converted into an OpArg.
This commit makes RCOpArg::IsImm check whether RCOpArg::Location returns
an immediate, so that RCOpArg::IsImm returns false when a host register
is being used. Code that wants to know whether an immediate exists in
the register cache rather than whether an immediate is currently being
used should call RegCache::IsImm instead.
Recently came across a strange issue where Dolphin would hard crash in most games with this error:
```sh
/usr/include/c++/15.2.1/optional:1165: constexpr const _Tp* std::optional<_Tp>::operator->() const [with _Tp = InputCommon::ImagePixelData]: Assertion 'this->_M_is_engaged()' failed.
```
The culprit turned out to be accessing `host_key_image` which is an `std::optional` thay may return `std::nullopt`. I'm not sure why this issue started occuring for me since I've had no issue with my Dynamic Input Textures in the past? But this fixes a crash if the image fails to load.
Aside from being unnecessary, on Windows the flag prevents two instances
of Dolphin (one instance from before 2509-371 when the flag was
introduced and the other after) from running the same ROM
simultaneously.
Attempting to do so generated the false error `"[Rom]" is an invalid
GCM/ISO file, or is not a GC/Wii ISO.` followed by `Failed to init core`
and emulation shutdown on the second instance to start the game. Fixing
the incorrect error message is a task I'm deferring to another PR.
The problem didn't happen when both instances were 2509-371 or later,
but I ran into it while bisecting an issue and it'd be nice to avoid
that problem in the future.
We have an optimization where the guest carry flag is kept in the host
carry flag between certain back-to-back pairs of integer instructions.
If the second instruction falls back to the interpreter, then
FallBackToInterpreter should flush the carry flag to m_ppc_state,
otherwise the interpreter reads a stale carry flag and at some later
point Jit64 trips the "Attempt to modify flags while flags locked!"
assertion.
An alternative solution would be to not store the guest carry flag in
the host carry flag to begin with if we know the next instruction is
going to fall back to the interpreter, but knowing that in advance is
non-trivial. Since interpreter fallbacks aren't exactly intended to be
super optimized, I went for the flushing solution instead, which is how
JitArm64 already works. In most cases, the emitted code shouldn't even
differ between these two solutions.
Note that the problematic situation only happens if the first integer
instruction doesn't fall back to the interpreter but the second one
does. This used to be impossible because there's no "JIT disable"
setting that's granular enough to disable some integer instructions but
not all, but with the constant propagation PR, it's possible if constant
propagation is able to entirely evaluate the first instruction but not
the second.
This makes SetUID take more emulated time giving the host more time to actually do the work.
The Wii menu "Data Management" -> "Save Data" -> "Wii" screen is no longer nearly as hard to emulate at full speed.
Constructing the UIDSys from the filesystem is a major bottleneck in the Wii menu "Data Management" -> "Save Data" -> "Wii" screen and this change makes it about twice as fast.
Call `UseNoImm` instead of `Use` on parameter `a` of `MultiplyImmediate`
since `Ra` gets passed to `IMUL` which asserts that parameter is not an
immediate.
There was a LINUX check added in b3bdad4, but this should be removed as this change applies to all Qt supported platforms. Simply put, GuiPrivate CMake files were introduced in Qt 6.9 and are now enforced in Qt 6.10 and are not platform-dependent.
Having it be static leads to a race condition if two different threads
call RunOnCPUThread with wait_for_completion set to true. (There's
currently nobody calling RunOnCPUThread from anything other than the
host thread, so this hasn't led to any consequences yet.)
The optimizations for subfcx introduced in #13852 also apply to subfx.
Rather than duplicating the logic, we merge the handlers, like we did
in #10120 for x86.
In 405baed805, we made the assumption that whether OpenAndroidContent
is able to create a new file depends on the open mode, the document
provider, and the positions of the celestial bodies. However, I'm
fairly sure that it can't create files at all as currently implemented.
First, ContentResolver.openFileDescriptor is documented as throwing
FileNotFoundException if the file doesn't exist. Now, the SAF
documentation is notoriously unreliable on matters like these, and
document providers can do whatever they want anyway, so we can't
actually trust this to mean that FileNotFoundException will always
be thrown if the file doesn't exist. But second, the Dolphin function
ContentHandler.unmangle is also unable to handle files that don't
already exist (unless the passed-in URI isn't mangled to begin with,
but to the best of my knowledge, there's no way to get a non-mangled URI
for a file that doesn't exist (unless you make assumptions about how the
document provider's URI scheme works, which we don't do in Dolphin)).
Summed up, it looks a lot like OpenAndroidContent can't create files,
or at the very least can't do so reliably.
Therefore I'm making DirectIOFile throw an assertion and return false
in situations where it's being asked to create a file under SAF. For
reference, there's no code in Dolphin that actually tries to create a
file under SAF. In all instances where we use SAF to write to files, the
file is created by the system file picker before it returns the URI of
the file to us.
What does this change gain us? First, if someone in the future tries to
use DirectIOFile to create a file under SAF, they'll be immediately
informed that this isn't supported rather than discovering it doesn't
work and chalking it up to SAF being bad for unpredictable reasons.
Second, we get rid of a call to File::Exists, which is a notable
performance improvement for game list scanning due to SAF and Dolphin's
"unmangling" being bad for reasons that unfortunately are entirely
predictable to those of us who have stared into the SAF void.
I've tested that game list scanning and game conversion still works.
When BindToRegister is called, the register cache marks the relevant
guest register as no longer containing an immediate. However, subfcx was
calling GetImm after BindToRegister. This led to a lot of panic alerts
after 2995aa5be4 added an assert to GetImm to check that the passed-in
register is an immediate.
Both before and after 2995aa5be4, the actual value of the immediate
wasn't overwritten by BindForRegister, only the fact that the register
is an immediate. Because of this, the emitted code happened to work
correctly.
If the build is an Android build, identify it as such in the AchievementManager user agent so that android builds can be tracked separately for debug purposes.
This fixes a regression in ac84ea17a6. That commit was intended to
reduce the number of MapInMemoryRegion calls while otherwise keeping
behavior the same, but it accidentally messed up our logic for writing
to m_logical_page_mappings. This commit restores the old
m_logical_page_mappings behavior, fixing a crash that would occur when
running JitArm64 without fastmem.
Like Jit64, JitArm64 now keeps track of the location of a guest register
using three booleans: Whether it is in ppcState, whether it is in a host
register, and whether it is a known immediate. The RegType enum remains
only for the purpose of keeping track of what format FPRs are stored in
in host registers.
Like the previous commit did for Jit64, JitArm64 can now handle the
combination of a value simultaneously being in a host register and being
a known immediate.
Unlike with Jit64, I've put the codegen-affecting changes in this commit
and the move away from the RegType enum in a follow-up commit. This is
in part because the design of JitArm64 made it easy to implement the
codegen-affecting changes without combining it with a big bang
refactorization, and in part because we need to keep RegType around for
keeping track of different float formats in Arm64FPRCache, complicating
the refactorization a bit.
They're now stored in ConstantPropagation instead.
I've also removed the LocationType enum. The location of each guest
register is now tracked using three booleans: Whether it is in ppcState,
whether it is in a host register, and whether it is a known immediate.
The first two of these booleans are stored in the register cache, and
the last one is stored in ConstantPropagation. This new model allows us
to handle the combination of a value simultaneously being in a host
register and being a known immediate. It also keeps track of which
registers are dirty, which was previously kept track of in X64CachedReg.
The old model maps to the new model as follows:
default host_reg immediate
Default true false false
Discarded false false false
Bound (!dirty) true false
Immediate false false true
SpeculativeImmediate true false true
[previously unrepresentable] (!dirty) true true
This commit makes the JIT set/clear the individual registers of
ConstantPropagation immediately instead of at the end of the
instruction. This is needed to prevent Jit64::ComputeRC, which reads
from a register written to earlier during the same instruction, from
reading back stale register values from ConstantPropagation in the next
commit.
To find out whether a host register needs to be unlocked, FlushRegisters
checks if the guest register is known to be a zero immediate. This works
right now, but it will stop working correctly once we gain the ability
to have a guest register be a known immediate and be in a host register
at the same time, because a register that's known to be a zero immediate
may have had a host register allocated prior to the call to
FlushRegisters. Instead, we should check whether the register is
RegType::Register after we're done calling BindForRead.
Restructuring things in this way brings two immediate benefits:
* Code is deduplicated between Jit64 and JitArm64.
* Materializing an immediate value in a register no longer results in us
forgetting what the immediate value was.
As a more long-term benefit, this lets us also run constant propagation
as part of PPCAnalyst, which could let us do cool stuff in the future
like statically determining whether a conditional branch will be taken.
But I have nothing concrete planned for that right now.
warning: definition of implicit copy constructor for 'BitField<2, 2, ColorChannel>' is deprecated because it has a user-declared copy assignment operator [-Wdeprecated-copy]
Redundant access specifier has the same accessibility as the previous access specifier.
Fusion is one of the built-in styles that Qt ships with, and that is
generally supported in all platforms and handles custom color palettes
properly.
The color palettes have been borrowed from the Dolphin Memory Engine
buddy application.
The new styles are:
- **Fusion Light**
- **Fusion Dark Gray**
- **Fusion Dark**
A demo of each style on Windows:
| System | Light | Dark | Fusion Light | Fusion Dark Gray | Fusion Dark |
| ------ | ----- | ---- | ------------ | ---------------- | ----------- |
| <img width="1920" height="1080" alt="Dolphin Emulator System style" src="https://github.com/user-attachments/assets/7f55a19d-d9a1-43d1-a435-1e1d5b29abe2" /> | <img width="1920" height="1080" alt="Dolphin Emulator Light style" src="https://github.com/user-attachments/assets/4c70f2f9-16b8-4777-b72b-55b2dffcd1e4" /> | <img width="1920" height="1080" alt="Dolphin Emulator Dark style" src="https://github.com/user-attachments/assets/5e669477-d2a5-4d19-b2c5-a2ed9bb1e6fe" /> | <img width="1920" height="1080" alt="Dolphin Emulator Light Fusion style" src="https://github.com/user-attachments/assets/b1f95c47-0691-4809-bd74-99e913c17684" /> | <img width="1920" height="1080" alt="Dolphin Emulator Dark Gray Fusion style" src="https://github.com/user-attachments/assets/c9d30aa3-f941-4fc5-806f-d3fbd2cae0cc" /> | <img width="1920" height="1080" alt="Dolphin Emulator Dark Fusion style" src="https://github.com/user-attachments/assets/123d2125-e126-4e8c-aa42-793ded8ffacc" /> |
> [!NOTE]
> Notice that the **Light** and **Dark** styles remain available only on
> Windows due to limitations on how styles in the various platforms
> handle (or mishandle) custom color palettes.
> [!IMPORTANT]
> Due to [`KDE-511547`](https://bugs.kde.org/show_bug.cgi?id=511547),
> after having used Breeze (default style in Plasma systems), top tool
> bars in tools areas will lose their correct background color; a
> restart will be required in order to get the updated color in the main
> window.
>
> UPDATE: The issue has been fixed upstream and should be available in
> KDE Plama 6.5.3 and newer.
The core no longer cares which thread is the host thread.
Cleaning up Android's HostThreadLock is left for another PR, in part
because the HostThreadLock in NativeConfig.cpp still serves a purpose,
and in part to make any issues easier to bisect.
By letting threads other than the host thread use things like
CPUThreadGuard, we can do a significant cleanup in AchievementsManager
in a later commit of this pull request.
Note: Some functions still can't be called from the CPU thread (or
threads the CPU thread might block on, like the GPU thread), but can
be called from any other thread.
We can now route Android analytics through Common::HttpAnalyticsBackend, drop the Volley sender, and keep the JNI layer limited to only transfer metadata since https://bugs.dolphin-emu.org/issues/11772 has been fixed.
Push and wait on WorkQueueThread items using PushBlocking. Previously we
created a Common::Event sync_event on the caller's stack, called Wait on
it, then had the WorkQueueThread call Set on the sync_event once the
thread was done.
In addition to being simpler the new way avoids a use-after-free that
could happen in convoluted and unlikely yet possible thread scheduling
sequences.
One such case can be triggered as follows:
* Set your audio backend to Cubeb
* In CubebStream::SetVolume set a breakpoint at the call to Wait and at
the call to cubeb_stream_set_volume.
* Start a game.
* Continue until the Cubeb Worker thread hits the
cubeb_stream_set_volume breakpoint and Emuthread hits the Wait
breakpoint, freezing each thread when it hits its breakpoint.
* Unfreeze Cubeb Worker.
* In Event::Set set a breakpoint at the end of the scope containing the
lock_guard such that the guard has been constructed but not destructed
when the breakpoint is hit.
* Continue until that breakpoint is hit by Cubeb Worker. If other
threads hit it first keep going.
* Freeze Cubeb Worker.
* For convenience remove the breakpoint in Event::Set so other threads
don't trigger it.
* In CubebStream::SetRunning set a breakpoint at the call to Wait.
* Unfreeze Emuthread and continue until the breakpoint is hit.
* In Cubeb Worker go to Event::Set and examine the values of m_mutex's
member variables. In Visual Studio Debug these are locking_thread_id
== 0xcccccc01 and ownership_levels == 0xcccccccc. This is the result
of Visual Studio overwriting the memory used on the stack by
sync_event in CubebStream::SetVolume with cc bytes to represent
uninitialized memory on the stack (since that function already
returned), and then allocating enough memory on the stack when calling
AudioCommon::SetSoundStreamRunning and then CubebStream::SetRunning
that it overwrote one byte of the memory formerly occupied by
locking_thread_id.
* If you unfreeze Cubeb Worker at this point it will trigger the lock
guard's destructor which will then try to unlock m_mutex. Since
m_mutex is no longer in scope this is a use-after-free, and in VS
debug triggers a debug assert due to locking_thread_id not matching
the current thread id.
Displays a different message at game launch if RetroAchievements fails specifically due to an invalid or expired API token, instructing the player to log back in with the game closed.
When the interpreter calls MSRUpdated, we should update the membase
variable. Not because the interpreter itself needs it, but because the
JIT needs it if it's falling back to the interpreter for an instruction
that sets the MSR.
Additionally, the JIT's FallBackToInterpreter needs to read back the new
membase value afterwards.
This fixes games crashing on JitArm64 if mtmsr is set to fall back to
interpreter. I was unable to reproduce the issue on Jit64, presumably
due to a fortunate series of coincidences (instructions that set MSR are
always followed by an exception exit, and
PowerPCManager::CheckExternalExceptions was always calling
JitInterface::UpdateMembase, and Jit64::WriteExceptionExit was always
calling Jit64::EmitUpdateMembase.)
This is a hassle-free BBA option intended for local play with multiple
Dolphin instances running *in the same system*. After selecting
**Broadband Adapter (IPC)** in the **SP1** slot in the GameCube section
in the settings, games that support LAN play will be able to discover
each other, without requiring third-party software or relatively complex
TAP setups.
The implementation is based on cpp-ipc, a high-performance inter-process
communication library that uses shared memory as transport layer.
Supported platforms are:
- [x] Linux
- [x] Windows
- [ ] macOS (cpp-ipc does not support this platform)
- [ ] FreeBSD (cpp-ipc does not support this platform)
- [ ] Android (cpp-ipc needs some adjustments; while it could work,
launching two Dolphin instances within the same Android system may be
both challenging and impractical)
PauseAndLock is now only called with do_lock=true, and unpause_on_unlock
only ever was used when do_lock is false (which is now handled in
RestoreStateAndUnlock instead), so both parameters are unnecessary.
Replace calls of PauseAndLock(do_lock=false) with new function
RestoreStateAndUnlock for clarity.
Callers of PauseAndLock ignored the return value when do_lock is
false, so RestoreStateAndUnlock doesn't need to return anything.
RestoreStateAndUnlock was only called with control_adjacent=true. Remove
the parameter and unconditionally call the function that was gated
behind it being true.
CPUManager::PauseAndLock is now only called with do_lock=true, and
unpause_on_unlock only ever was used when do_lock is false (which is now
handled in RestoreStateAndUnlock instead), so both parameters are
unnecessary.
Replace call of CPUManager::PauseAndLock(do_lock=false) with new
function RestoreStateAndUnlock for clarity.
Callers of Core::PauseAndLock ignore the return value when do_lock is
false, so in that case was_unpaused in Core::PauseAndLock doesn't need
to be set and so RestoreStateAndUnlock doesn't need to return anything.
Make s_have_fake_cpu_thread a class member instead. In addition to
getting rid of a bit of static state, this simplifies refactoring in an
upcoming commit.
OProfile is not used at all these days, most major distributions do not ship it anymore (Debian, Fedora, and Alpine to name the few I've checked) and following a discussion on Discord, nobody is apparently using it, most devs not even being aware of it. This removes an optional dependency from Dolphin.
Allow loading a custom font by naming it OSD_Font.ttf and placing the file in the User/Load folder, to load that font instead. Useful for future testing.
Don't log a warning in GetPropertyHelper when the property isn't
present. The function returns an optional, so any callers that want to
log a warning when nullopt is returned can do so themselves.
This prevents plugged-in devices (an Xbox One controller in my case)
from spamming the message "W[COMMON]: CM_Get_DevNode_Property returned:
37" twice per second (that value being CR_NO_SUCH_VALUE).
Added some new visibility toggles so players can choose what to show (or hide) during their RetroAchievements runs:
Toggle for the Speedrun Leaderboards timer
Toggle for Achievement Challenge badges
Check if the return value of std::filesystem::remove_all is -1 rather
than 0; the former is the specified return value if there's an error
while 0 just means the directory already didn't exist (which is the end
result we want).
Previously error messages such as the following were possible:
E[COMMON]: DeleteDirRecursively: [path]/User/RedirectSession/ failed The
operation completed successfully.
Also adds a period in the error string to make it look nicer.
Rename m_session to m_last_value_session to better reflect its meaning
and distinguish it from m_current_value_session which will be introduced
in another commit.