mirror of
https://git.naxdy.org/Mirror/Ryujinx.git
synced 2026-08-17 06:15:32 +00:00
Thread process identity into PTC cache ownership
This commit is contained in:
parent
a82350bb77
commit
33fe1e8cea
12 changed files with 143 additions and 39 deletions
|
|
@ -34,7 +34,7 @@ namespace ARMeilleure.Translation.PTC
|
|||
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
||||
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
||||
|
||||
private const uint InternalVersion = 7020; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
private const uint InternalVersion = 7020; //! To be incremented manually for each change to the ARMeilleure project. Your value was 7031, keeping this comment here just so you have the reference.
|
||||
|
||||
private const string ActualDir = "0";
|
||||
private const string BackupDir = "1";
|
||||
|
|
@ -71,6 +71,7 @@ namespace ARMeilleure.Translation.PTC
|
|||
|
||||
public string TitleIdText { get; private set; }
|
||||
public string DisplayVersion { get; private set; }
|
||||
public PtcCacheInfo CacheInfo { get; private set; }
|
||||
|
||||
private MemoryManagerType _memoryMode;
|
||||
|
||||
|
|
@ -101,6 +102,7 @@ namespace ARMeilleure.Translation.PTC
|
|||
|
||||
TitleIdText = TitleIdTextDefault;
|
||||
DisplayVersion = DisplayVersionDefault;
|
||||
CacheInfo = new PtcCacheInfo(0, TitleIdTextDefault, TitleIdTextDefault, 0, DisplayVersionDefault, "Unknown", "default");
|
||||
|
||||
CachePathActual = string.Empty;
|
||||
CachePathBackup = string.Empty;
|
||||
|
|
@ -108,19 +110,34 @@ namespace ARMeilleure.Translation.PTC
|
|||
Disable();
|
||||
}
|
||||
|
||||
public void Initialize(string titleIdText, string displayVersion, bool enabled, MemoryManagerType memoryMode, string cacheSelector)
|
||||
public void Initialize(PtcCacheInfo cacheInfo, bool enabled, MemoryManagerType memoryMode)
|
||||
{
|
||||
Wait();
|
||||
|
||||
Profiler.Wait();
|
||||
Profiler.ClearEntries();
|
||||
|
||||
Logger.Info?.Print(LogClass.Ptc, $"Initializing Profiled Persistent Translation Cache v{InternalVersion}\n\t\t (title: {titleIdText}, version: '{displayVersion}', selector: '{cacheSelector}', enabled: {enabled}).");
|
||||
CacheInfo = cacheInfo;
|
||||
|
||||
if (!enabled || string.IsNullOrEmpty(titleIdText) || titleIdText == TitleIdTextDefault)
|
||||
Logger.Info?.Print(
|
||||
LogClass.Ptc,
|
||||
$"Initializing Profiled Persistent Translation Cache v{InternalVersion}\n\t\t " +
|
||||
$"(pid: {cacheInfo.ProcessId}, title: {cacheInfo.TitleIdText}, application: {cacheInfo.ApplicationIdText}, " +
|
||||
$"programIndex: {cacheInfo.ProgramIndex}, version: '{cacheInfo.DisplayVersion}', kind: {cacheInfo.ProcessKind}, " +
|
||||
$"selector: '{cacheInfo.CacheSelector}', key: '{cacheInfo.CacheKey}', enabled: {enabled}).");
|
||||
|
||||
if (!enabled || string.IsNullOrEmpty(cacheInfo.TitleIdText) || cacheInfo.TitleIdText == TitleIdTextDefault)
|
||||
{
|
||||
TitleIdText = TitleIdTextDefault;
|
||||
DisplayVersion = DisplayVersionDefault;
|
||||
CacheInfo = new PtcCacheInfo(
|
||||
cacheInfo.ProcessId,
|
||||
TitleIdText,
|
||||
cacheInfo.ApplicationIdText,
|
||||
cacheInfo.ProgramIndex,
|
||||
DisplayVersion,
|
||||
cacheInfo.ProcessKind,
|
||||
cacheInfo.CacheSelector);
|
||||
|
||||
CachePathActual = string.Empty;
|
||||
CachePathBackup = string.Empty;
|
||||
|
|
@ -130,8 +147,16 @@ namespace ARMeilleure.Translation.PTC
|
|||
return;
|
||||
}
|
||||
|
||||
TitleIdText = titleIdText;
|
||||
DisplayVersion = !string.IsNullOrEmpty(displayVersion) ? displayVersion : DisplayVersionDefault;
|
||||
TitleIdText = cacheInfo.TitleIdText;
|
||||
DisplayVersion = !string.IsNullOrEmpty(cacheInfo.DisplayVersion) ? cacheInfo.DisplayVersion : DisplayVersionDefault;
|
||||
CacheInfo = new PtcCacheInfo(
|
||||
cacheInfo.ProcessId,
|
||||
TitleIdText,
|
||||
cacheInfo.ApplicationIdText,
|
||||
cacheInfo.ProgramIndex,
|
||||
DisplayVersion,
|
||||
cacheInfo.ProcessKind,
|
||||
cacheInfo.CacheSelector);
|
||||
_memoryMode = memoryMode;
|
||||
|
||||
string workPathActual = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", ActualDir);
|
||||
|
|
@ -147,8 +172,14 @@ namespace ARMeilleure.Translation.PTC
|
|||
Directory.CreateDirectory(workPathBackup);
|
||||
}
|
||||
|
||||
CachePathActual = Path.Combine(workPathActual, DisplayVersion) + "-" + cacheSelector;
|
||||
CachePathBackup = Path.Combine(workPathBackup, DisplayVersion) + "-" + cacheSelector;
|
||||
CachePathActual = Path.Combine(workPathActual, DisplayVersion) + "-" + CacheInfo.CacheSelector;
|
||||
CachePathBackup = Path.Combine(workPathBackup, DisplayVersion) + "-" + CacheInfo.CacheSelector;
|
||||
|
||||
Logger.Info?.Print(
|
||||
LogClass.Ptc,
|
||||
$"PPTC cache owner selected (pid: {CacheInfo.ProcessId}, title: {TitleIdText}, application: {CacheInfo.ApplicationIdText}, " +
|
||||
$"version: '{DisplayVersion}', kind: {CacheInfo.ProcessKind}, selector: '{CacheInfo.CacheSelector}', " +
|
||||
$"key: '{CacheInfo.CacheKey}', path: '{CachePathActual}').");
|
||||
|
||||
PreLoad();
|
||||
Profiler.PreLoad();
|
||||
|
|
@ -427,7 +458,12 @@ namespace ARMeilleure.Translation.PTC
|
|||
|
||||
long fileSize = new FileInfo(fileName).Length;
|
||||
|
||||
Logger.Info?.Print(LogClass.Ptc, $"{(isBackup ? "Loaded Backup Translation Cache" : "Loaded Translation Cache")} (size: {fileSize} bytes, translated functions: {GetEntriesCount()}).");
|
||||
Logger.Info?.Print(
|
||||
LogClass.Ptc,
|
||||
$"{(isBackup ? "Loaded Backup Translation Cache" : "Loaded Translation Cache")} " +
|
||||
$"(pid: {CacheInfo.ProcessId}, title: {TitleIdText}, version: '{DisplayVersion}', kind: {CacheInfo.ProcessKind}, " +
|
||||
$"selector: '{CacheInfo.CacheSelector}', key: '{CacheInfo.CacheKey}', path: '{fileName}', " +
|
||||
$"size: {fileSize} bytes, translated functions: {GetEntriesCount()}).");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -569,7 +605,11 @@ namespace ARMeilleure.Translation.PTC
|
|||
|
||||
if (fileSize != 0L)
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Ptc, $"Saved Translation Cache (size: {fileSize} bytes, translated functions: {translatedFuncsCount}).");
|
||||
Logger.Info?.Print(
|
||||
LogClass.Ptc,
|
||||
$"Saved Translation Cache (pid: {CacheInfo.ProcessId}, title: {TitleIdText}, version: '{DisplayVersion}', " +
|
||||
$"kind: {CacheInfo.ProcessKind}, selector: '{CacheInfo.CacheSelector}', key: '{CacheInfo.CacheKey}', " +
|
||||
$"path: '{fileName}', size: {fileSize} bytes, translated functions: {translatedFuncsCount}).");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
33
src/ARMeilleure/Translation/PTC/PtcCacheInfo.cs
Normal file
33
src/ARMeilleure/Translation/PTC/PtcCacheInfo.cs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
namespace ARMeilleure.Translation.PTC
|
||||
{
|
||||
public readonly struct PtcCacheInfo
|
||||
{
|
||||
public ulong ProcessId { get; }
|
||||
public string TitleIdText { get; }
|
||||
public string ApplicationIdText { get; }
|
||||
public byte ProgramIndex { get; }
|
||||
public string DisplayVersion { get; }
|
||||
public string ProcessKind { get; }
|
||||
public string CacheSelector { get; }
|
||||
|
||||
public string CacheKey => $"{DisplayVersion}-{CacheSelector}";
|
||||
|
||||
public PtcCacheInfo(
|
||||
ulong processId,
|
||||
string titleIdText,
|
||||
string applicationIdText,
|
||||
byte programIndex,
|
||||
string displayVersion,
|
||||
string processKind,
|
||||
string cacheSelector)
|
||||
{
|
||||
ProcessId = processId;
|
||||
TitleIdText = titleIdText ?? string.Empty;
|
||||
ApplicationIdText = applicationIdText ?? string.Empty;
|
||||
ProgramIndex = programIndex;
|
||||
DisplayVersion = displayVersion ?? string.Empty;
|
||||
ProcessKind = processKind ?? string.Empty;
|
||||
CacheSelector = string.IsNullOrEmpty(cacheSelector) ? "default" : cacheSelector;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@ namespace ARMeilleure.Translation.PTC
|
|||
{
|
||||
private const string OuterHeaderMagicString = "Pohd\0\0\0\0";
|
||||
|
||||
private const uint InternalVersion = 7007; //! Not to be incremented manually for each change to the ARMeilleure project.
|
||||
private const uint InternalVersion = 7028; //! Not to be incremented manually for each change to the ARMeilleure project.
|
||||
|
||||
private static readonly uint[] _migrateInternalVersions =
|
||||
[
|
||||
|
|
@ -275,7 +275,12 @@ namespace ARMeilleure.Translation.PTC
|
|||
|
||||
long fileSize = new FileInfo(fileName).Length;
|
||||
|
||||
Logger.Info?.Print(LogClass.Ptc, $"{(isBackup ? "Loaded Backup Profiling Info" : "Loaded Profiling Info")} (size: {fileSize} bytes, profiled functions: {ProfiledFuncs.Count}).");
|
||||
Logger.Info?.Print(
|
||||
LogClass.Ptc,
|
||||
$"{(isBackup ? "Loaded Backup Profiling Info" : "Loaded Profiling Info")} " +
|
||||
$"(pid: {_ptc.CacheInfo.ProcessId}, title: {_ptc.TitleIdText}, version: '{_ptc.DisplayVersion}', " +
|
||||
$"kind: {_ptc.CacheInfo.ProcessKind}, selector: '{_ptc.CacheInfo.CacheSelector}', key: '{_ptc.CacheInfo.CacheKey}', " +
|
||||
$"path: '{fileName}', size: {fileSize} bytes, profiled functions: {ProfiledFuncs.Count}).");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -396,7 +401,11 @@ namespace ARMeilleure.Translation.PTC
|
|||
|
||||
if (fileSize != 0L)
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Ptc, $"Saved Profiling Info (size: {fileSize} bytes, profiled functions: {profiledFuncsCount}).");
|
||||
Logger.Info?.Print(
|
||||
LogClass.Ptc,
|
||||
$"Saved Profiling Info (pid: {_ptc.CacheInfo.ProcessId}, title: {_ptc.TitleIdText}, version: '{_ptc.DisplayVersion}', " +
|
||||
$"kind: {_ptc.CacheInfo.ProcessKind}, selector: '{_ptc.CacheInfo.CacheSelector}', key: '{_ptc.CacheInfo.CacheKey}', " +
|
||||
$"path: '{fileName}', size: {fileSize} bytes, profiled functions: {profiledFuncsCount}).");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -61,9 +61,9 @@ namespace ARMeilleure.Translation
|
|||
FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub;
|
||||
}
|
||||
|
||||
public IPtcLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector)
|
||||
public IPtcLoadState LoadDiskCache(PtcCacheInfo cacheInfo, bool enabled)
|
||||
{
|
||||
_ptc.Initialize(titleIdText, displayVersion, enabled, Memory.Type, cacheSelector);
|
||||
_ptc.Initialize(cacheInfo, enabled, Memory.Type);
|
||||
return _ptc;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using ARMeilleure.Memory;
|
||||
using ARMeilleure.Translation.PTC;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Cpu.AppleHv
|
||||
|
|
@ -32,7 +33,7 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
{
|
||||
}
|
||||
|
||||
public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector)
|
||||
public IDiskCacheLoadState LoadDiskCache(PtcCacheInfo cacheInfo, bool enabled)
|
||||
{
|
||||
return new DummyDiskCacheLoadState();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using ARMeilleure.Translation.PTC;
|
||||
|
||||
namespace Ryujinx.Cpu
|
||||
{
|
||||
|
|
@ -44,11 +45,10 @@ namespace Ryujinx.Cpu
|
|||
/// <remarks>
|
||||
/// If the execution engine is recompiling guest code, this can be used to load cached code from disk.
|
||||
/// </remarks>
|
||||
/// <param name="titleIdText">Title ID of the application in padded hex form</param>
|
||||
/// <param name="displayVersion">Version of the application</param>
|
||||
/// <param name="cacheInfo">Identity and selector for the process-owned disk cache</param>
|
||||
/// <param name="enabled">True if the cache should be loaded from disk if it exists, false otherwise</param>
|
||||
/// <returns>Disk cache load progress reporter and manager</returns>
|
||||
IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector);
|
||||
IDiskCacheLoadState LoadDiskCache(PtcCacheInfo cacheInfo, bool enabled);
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that code has been loaded into guest memory, and that it might be executed in the future.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using ARMeilleure.Common;
|
||||
using ARMeilleure.Memory;
|
||||
using ARMeilleure.Translation;
|
||||
using ARMeilleure.Translation.PTC;
|
||||
using Ryujinx.Cpu.Signal;
|
||||
|
||||
namespace Ryujinx.Cpu.Jit
|
||||
|
|
@ -51,9 +52,9 @@ namespace Ryujinx.Cpu.Jit
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector)
|
||||
public IDiskCacheLoadState LoadDiskCache(PtcCacheInfo cacheInfo, bool enabled)
|
||||
{
|
||||
return new JitDiskCacheLoadState(_translator.LoadDiskCache(titleIdText, displayVersion, enabled, cacheSelector));
|
||||
return new JitDiskCacheLoadState(_translator.LoadDiskCache(cacheInfo, enabled));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using ARMeilleure.Common;
|
||||
using ARMeilleure.Memory;
|
||||
using ARMeilleure.Translation.PTC;
|
||||
using Ryujinx.Cpu.Jit;
|
||||
using Ryujinx.Cpu.LightningJit.State;
|
||||
|
||||
|
|
@ -46,7 +47,7 @@ namespace Ryujinx.Cpu.LightningJit
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector)
|
||||
public IDiskCacheLoadState LoadDiskCache(PtcCacheInfo cacheInfo, bool enabled)
|
||||
{
|
||||
return new DummyDiskCacheLoadState();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using ARMeilleure.Memory;
|
||||
using ARMeilleure.Translation.PTC;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.Graphics.Gpu;
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
|
|
@ -9,12 +10,10 @@ namespace Ryujinx.HLE.HOS
|
|||
interface IArmProcessContext : IProcessContext
|
||||
{
|
||||
IDiskCacheLoadState Initialize(
|
||||
string titleIdText,
|
||||
string displayVersion,
|
||||
PtcCacheInfo cacheInfo,
|
||||
bool diskCacheEnabled,
|
||||
ulong codeAddress,
|
||||
ulong codeSize,
|
||||
string cacheSelector);
|
||||
ulong codeSize);
|
||||
}
|
||||
|
||||
class ArmProcessContext<T> : IArmProcessContext where T : class, IVirtualMemoryManagerTracked, IMemoryManager
|
||||
|
|
@ -64,15 +63,13 @@ namespace Ryujinx.HLE.HOS
|
|||
}
|
||||
|
||||
public IDiskCacheLoadState Initialize(
|
||||
string titleIdText,
|
||||
string displayVersion,
|
||||
PtcCacheInfo cacheInfo,
|
||||
bool diskCacheEnabled,
|
||||
ulong codeAddress,
|
||||
ulong codeSize,
|
||||
string cacheSelector)
|
||||
ulong codeSize)
|
||||
{
|
||||
_cpuContext.PrepareCodeRange(codeAddress, codeSize);
|
||||
return _cpuContext.LoadDiskCache(titleIdText, displayVersion, diskCacheEnabled, cacheSelector);
|
||||
return _cpuContext.LoadDiskCache(cacheInfo, diskCacheEnabled);
|
||||
}
|
||||
|
||||
public void InvalidateCacheRegion(ulong address, ulong size)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using ARMeilleure.Translation.PTC;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Cpu;
|
||||
|
|
@ -7,6 +8,7 @@ using Ryujinx.Cpu.LightningJit;
|
|||
using Ryujinx.Graphics.Gpu;
|
||||
using Ryujinx.HLE.HOS.Kernel;
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
using Ryujinx.HLE.Loaders.Processes;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
|
@ -17,8 +19,10 @@ namespace Ryujinx.HLE.HOS
|
|||
{
|
||||
private readonly ITickSource _tickSource;
|
||||
private readonly GpuContext _gpu;
|
||||
private readonly string _titleIdText;
|
||||
private readonly ulong _programId;
|
||||
private readonly byte _programIndex;
|
||||
private readonly string _displayVersion;
|
||||
private readonly ProcessKind _processKind;
|
||||
private readonly bool _diskCacheEnabled;
|
||||
private readonly string _diskCacheSelector;
|
||||
private readonly ulong _codeAddress;
|
||||
|
|
@ -29,8 +33,10 @@ namespace Ryujinx.HLE.HOS
|
|||
public ArmProcessContextFactory(
|
||||
ITickSource tickSource,
|
||||
GpuContext gpu,
|
||||
string titleIdText,
|
||||
ulong programId,
|
||||
byte programIndex,
|
||||
string displayVersion,
|
||||
ProcessKind processKind,
|
||||
bool diskCacheEnabled,
|
||||
string diskCacheSelector,
|
||||
ulong codeAddress,
|
||||
|
|
@ -38,8 +44,10 @@ namespace Ryujinx.HLE.HOS
|
|||
{
|
||||
_tickSource = tickSource;
|
||||
_gpu = gpu;
|
||||
_titleIdText = titleIdText;
|
||||
_programId = programId;
|
||||
_programIndex = programIndex;
|
||||
_displayVersion = displayVersion;
|
||||
_processKind = processKind;
|
||||
_diskCacheEnabled = diskCacheEnabled;
|
||||
_diskCacheSelector = diskCacheSelector;
|
||||
_codeAddress = codeAddress;
|
||||
|
|
@ -119,8 +127,18 @@ namespace Ryujinx.HLE.HOS
|
|||
}
|
||||
|
||||
string cacheSelector = _diskCacheSelector ?? "default";
|
||||
string programIdText = _programId == 0 ? string.Empty : $"{_programId:x16}";
|
||||
string applicationIdText = _programId == 0 ? string.Empty : $"{_programId & ~0xFul:x16}";
|
||||
PtcCacheInfo cacheInfo = new(
|
||||
pid,
|
||||
programIdText,
|
||||
applicationIdText,
|
||||
_programIndex,
|
||||
_displayVersion,
|
||||
_processKind.ToString(),
|
||||
cacheSelector);
|
||||
|
||||
DiskCacheLoadState = processContext.Initialize(_titleIdText, _displayVersion, _diskCacheEnabled, _codeAddress, _codeSize, cacheSelector);
|
||||
DiskCacheLoadState = processContext.Initialize(cacheInfo, _diskCacheEnabled, _codeAddress, _codeSize);
|
||||
|
||||
return processContext;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -181,8 +181,10 @@ namespace Ryujinx.HLE.Loaders.Processes
|
|||
ArmProcessContextFactory processContextFactory = new(
|
||||
context.Device.System.TickSource,
|
||||
context.Device.Gpu,
|
||||
string.Empty,
|
||||
string.Empty,
|
||||
kip.ProgramId,
|
||||
0,
|
||||
kip.Version.ToString(),
|
||||
ProcessResult.GetProcessKind(kip.ProgramId),
|
||||
false,
|
||||
null,
|
||||
codeAddress,
|
||||
|
|
@ -376,8 +378,10 @@ namespace Ryujinx.HLE.Loaders.Processes
|
|||
ArmProcessContextFactory processContextFactory = new(
|
||||
context.Device.System.TickSource,
|
||||
context.Device.Gpu,
|
||||
$"{programId:x16}",
|
||||
programId,
|
||||
programIndex,
|
||||
displayVersion,
|
||||
ProcessResult.GetProcessKind(programId),
|
||||
diskCacheEnabled,
|
||||
diskCacheSelector,
|
||||
codeStart,
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ namespace Ryujinx.HLE.Loaders.Processes
|
|||
AllowCodeMemoryForJit = allowCodeMemoryForJit;
|
||||
}
|
||||
|
||||
private static ProcessKind GetProcessKind(ulong programId)
|
||||
internal static ProcessKind GetProcessKind(ulong programId)
|
||||
{
|
||||
if (programId == 0)
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue