Skip to content

Commit 53b6a1a

Browse files
committed
Bug 1878993 - [1/1] Inform Windows about different process types r=win-reviewers,ipc-reviewers,yjuglaret,nika
Windows has an ill-documented subsystem known as the Application Launch Prefetcher, which apparently preloads DLLs for a binary based on what DLLs that binary loaded last time. For Firefox, that's not a great heuristic; we relaunch ourselves as a subprocess with potentially completely different DLL settings. We're not the only such application, though, and Windows _does_ have a way to signal the ALPF to load different DLL sets depending on launch context. Differential Revision: https://phabricator.services.mozilla.com/D202275
1 parent d782bee commit 53b6a1a

File tree

3 files changed

+129
-3
lines changed

3 files changed

+129
-3
lines changed

browser/app/winlauncher/LauncherProcessWin.cpp

+6-2
Original file line numberDiff line numberDiff line change
@@ -401,8 +401,12 @@ Maybe<int> LauncherMain(int& argc, wchar_t* argv[],
401401
}
402402
#endif // defined(MOZ_LAUNCHER_PROCESS)
403403

404-
// Now proceed with setting up the parameters for process creation
405-
UniquePtr<wchar_t[]> cmdLine(MakeCommandLine(argc, argv));
404+
// Now proceed with setting up the parameters for process creation.
405+
constexpr static const wchar_t* extraArgs[] = {
406+
L"/prefetch:1", // for APFL; see ipc/glue/GeckoChildProcessHost.cpp
407+
};
408+
UniquePtr<wchar_t[]> cmdLine(
409+
MakeCommandLine(argc, argv, ARRAYSIZE(extraArgs), extraArgs));
406410
if (!cmdLine) {
407411
HandleLauncherError(LAUNCHER_ERROR_GENERIC());
408412
return Nothing();

ipc/glue/GeckoChildProcessHost.cpp

+100-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "base/task.h"
1414
#include "chrome/common/chrome_switches.h"
1515
#include "chrome/common/process_watcher.h"
16+
#include "mozilla/ProcessType.h"
1617
#ifdef MOZ_WIDGET_COCOA
1718
# include <bsm/libbsm.h>
1819
# include <mach/mach_traps.h>
@@ -66,6 +67,7 @@
6667
#ifdef XP_WIN
6768
# include <stdlib.h>
6869

70+
# include "mozilla/WindowsVersion.h"
6971
# include "nsIWinTaskbar.h"
7072
# define NS_TASKBAR_CONTRACTID "@mozilla.org/windows-taskbar;1"
7173

@@ -249,7 +251,7 @@ class BaseProcessLauncher {
249251
};
250252

251253
#ifdef XP_WIN
252-
class WindowsProcessLauncher : public BaseProcessLauncher {
254+
class WindowsProcessLauncher final : public BaseProcessLauncher {
253255
public:
254256
WindowsProcessLauncher(GeckoChildProcessHost* aHost,
255257
std::vector<std::string>&& aExtraOpts)
@@ -261,6 +263,9 @@ class WindowsProcessLauncher : public BaseProcessLauncher {
261263
virtual RefPtr<ProcessHandlePromise> DoLaunch() override;
262264
virtual Result<Ok, LaunchError> DoFinishLaunch() override;
263265

266+
private:
267+
void AddApplicationPrefetchArgument();
268+
264269
mozilla::Maybe<CommandLine> mCmdLine;
265270
bool mUseSandbox = false;
266271

@@ -1401,6 +1406,97 @@ Result<Ok, LaunchError> MacProcessLauncher::DoFinishLaunch() {
14011406
#endif // XP_MACOSX
14021407

14031408
#ifdef XP_WIN
1409+
void WindowsProcessLauncher::AddApplicationPrefetchArgument() {
1410+
// The Application Launch Prefetcher (ALPF) is an ill-documented Windows
1411+
// subsystem that's intended to speed up process launching, apparently mostly
1412+
// by assuming that a binary is going to want to load the same DLLs as it did
1413+
// the last time it launched, and getting those prepped for loading as well.
1414+
//
1415+
// For most applications, that's a good bet. For Firefox, it's less so, since
1416+
// we use the same binary with different arguments to do completely different
1417+
// things. Windows does allow applications to take up multiple slots in this
1418+
// cache, but the "which bucket does this invocation go in?" mechanism is
1419+
// highly unusual: the OS scans the command line and looks for a command-line
1420+
// switch of a particular form.
1421+
//
1422+
// (There is allegedly a way to do this without involving the command line,
1423+
// OVERRIDE_PREFETCH_PARAMETER, but it's even more poorly documented.)
1424+
1425+
// Applications' different prefetch-cache buckets are named with numbers from
1426+
// "1" to some OS-version-determined limit, with an additional implicit "0"
1427+
// cache bucket which is used when no valid prefetch cache slot is named.
1428+
//
1429+
// (The "0" bucket's existence and behavior is not documented, but has been
1430+
// confirmed by observing the creation and enumeration of cache files in the
1431+
// C:\Windows\Prefetch folder.)
1432+
static size_t const kMaxSlotNo = IsWin1122H2OrLater() ? 16 : 8;
1433+
1434+
// Determine the prefetch-slot number to be used for the process we're about
1435+
// to launch.
1436+
//
1437+
// This may be changed freely between Firefox versions, as a Firefox update
1438+
// will completely invalidate the prefetch cache anyway.
1439+
size_t const prefetchSlot = [&]() -> size_t {
1440+
switch (mProcessType) {
1441+
// This code path is not used when starting the main process...
1442+
case GeckoProcessType_Default:
1443+
// ...ForkServer is not used on Windows...
1444+
case GeckoProcessType_ForkServer:
1445+
// ..."End" isn't a process-type, just a limit...
1446+
case GeckoProcessType_End:
1447+
// ...and any new process-types should be considered explicitly here.
1448+
default:
1449+
MOZ_ASSERT_UNREACHABLE("Invalid process type");
1450+
return 0;
1451+
1452+
// We reserve 1 for the main process as started by the launcher process.
1453+
// (See LauncherProcessWin.cpp.) Otherwise, we mostly match the process-
1454+
// type enumeration.
1455+
case GeckoProcessType_Content:
1456+
return 2;
1457+
case GeckoProcessType_Socket:
1458+
return 3; // usurps IPDLUnitTest
1459+
case GeckoProcessType_GMPlugin:
1460+
return 4;
1461+
case GeckoProcessType_GPU:
1462+
return 5;
1463+
case GeckoProcessType_RemoteSandboxBroker:
1464+
return 6; // usurps VR
1465+
case GeckoProcessType_RDD:
1466+
return 7;
1467+
1468+
case GeckoProcessType_Utility: {
1469+
// Continue the enumeration, using the SandboxingKind as a
1470+
// probably-passably-precise proxy for the process's purpose.
1471+
//
1472+
// (On Win10 and earlier, or when sandboxing is not used, this will lump
1473+
// all utility processes into slot 8.)
1474+
# ifndef MOZ_SANDBOX
1475+
size_t const val = 0;
1476+
# else
1477+
size_t const val = static_cast<size_t>(mSandbox);
1478+
# endif
1479+
return std::min(kMaxSlotNo, 8 + val);
1480+
}
1481+
1482+
// These process types are started so rarely that we're not concerned
1483+
// about their interaction with the prefetch cache. Lump them together at
1484+
// the end (possibly alongside other process types).
1485+
case GeckoProcessType_IPDLUnitTest:
1486+
case GeckoProcessType_VR:
1487+
return kMaxSlotNo;
1488+
}
1489+
}();
1490+
MOZ_ASSERT(prefetchSlot <= kMaxSlotNo);
1491+
1492+
if (prefetchSlot == 0) {
1493+
// default; no explicit argument needed
1494+
return;
1495+
}
1496+
1497+
mCmdLine->AppendLooseValue(StringPrintf(L"/prefetch:%zu", prefetchSlot));
1498+
}
1499+
14041500
Result<Ok, LaunchError> WindowsProcessLauncher::DoSetup() {
14051501
Result<Ok, LaunchError> aError = BaseProcessLauncher::DoSetup();
14061502
if (aError.isErr()) {
@@ -1581,6 +1677,9 @@ Result<Ok, LaunchError> WindowsProcessLauncher::DoSetup() {
15811677
// Process type
15821678
mCmdLine->AppendLooseValue(UTF8ToWide(ChildProcessType()));
15831679

1680+
// Prefetch cache hint
1681+
AddApplicationPrefetchArgument();
1682+
15841683
# ifdef MOZ_SANDBOX
15851684
if (mUseSandbox) {
15861685
// Mark the handles to inherit as inheritable.

toolkit/xre/nsWindowsWMain.cpp

+23
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,32 @@ static void FreeAllocStrings(int argc, char** argv) {
111111
delete[] argv;
112112
}
113113

114+
// Remove "/prefetch:##" argument from the command line, if present. (See
115+
// GeckoChildProcessHost.cpp for details.)
116+
//
117+
// Colons are not permitted in path-elements on Windows, so a string of this
118+
// form is extremely unlikely to appear with the intent of being a legitimate
119+
// path-argument.
120+
void RemovePrefetchArguments(int& argc, WCHAR** argv) {
121+
size_t prefetchArgsCount [[maybe_unused]] = 0;
122+
for (int i = 0; i < argc; ++i) {
123+
constexpr const wchar_t prefix[] = L"/prefetch:";
124+
auto const cmp = wcsncmp(argv[i], prefix, ARRAYSIZE(prefix) - 1);
125+
if (cmp == 0) {
126+
std::copy(argv + i + 1, argv + argc, argv + i);
127+
--argc;
128+
--i;
129+
prefetchArgsCount++;
130+
}
131+
}
132+
MOZ_ASSERT(prefetchArgsCount <= 1,
133+
"at most one /prefetch:## argument should be present");
134+
}
135+
114136
int wmain(int argc, WCHAR** argv) {
115137
SanitizeEnvironmentVariables();
116138
SetDllDirectoryW(L"");
139+
RemovePrefetchArguments(argc, argv);
117140

118141
// Only run this code if LauncherProcessWin.h was included beforehand, thus
119142
// signalling that the hosting process should support launcher mode.

0 commit comments

Comments
 (0)