Skip to content

Commit f0dfb89

Browse files
authored
chore: add test to ensure dlls are lazily loaded on windows (#32648)
1 parent bad8119 commit f0dfb89

File tree

4 files changed

+157
-1
lines changed

4 files changed

+157
-1
lines changed

ext/webgpu/lib.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,22 @@ pub const UNSTABLE_FEATURE_NAME: &str = "webgpu";
4545
#[allow(clippy::print_stdout)]
4646
pub fn print_linker_flags(name: &str) {
4747
if cfg!(windows) {
48-
// these dls load slowly, so delay loading them
48+
// these dlls load slowly, so delay loading them
4949
let dlls = [
5050
// webgpu
5151
"d3dcompiler_47",
5252
"OPENGL32",
53+
"gdi32",
54+
"setupapi",
55+
"oleaut32",
56+
"combase",
5357
// network related functions
5458
"iphlpapi",
59+
// timer functions
60+
"winmm",
61+
// debugging/diagnostics (only needed on panic/crash)
62+
"dbghelp",
63+
"psapi",
5564
];
5665
for dll in dlls {
5766
println!("cargo:rustc-link-arg-bin={name}=/delayload:{dll}.dll");
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"if": "windows",
3+
"args": "run --allow-read check_dlls.ts",
4+
"output": "output.out"
5+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// reads the deno binary's PE headers and outputs all DLLs
2+
// along with whether they are eagerly or delay-loaded.
3+
4+
function readU16(buf: Uint8Array, offset: number): number {
5+
return buf[offset] | (buf[offset + 1] << 8);
6+
}
7+
8+
function readU32(buf: Uint8Array, offset: number): number {
9+
return (
10+
(buf[offset] |
11+
(buf[offset + 1] << 8) |
12+
(buf[offset + 2] << 16) |
13+
((buf[offset + 3] << 24) >>> 0)) >>>
14+
0
15+
);
16+
}
17+
18+
function readCString(buf: Uint8Array, offset: number): string {
19+
let end = offset;
20+
while (end < buf.length && buf[end] !== 0) end++;
21+
return new TextDecoder().decode(buf.subarray(offset, end));
22+
}
23+
24+
interface Section {
25+
name: string;
26+
virtualAddress: number;
27+
virtualSize: number;
28+
rawOffset: number;
29+
rawSize: number;
30+
}
31+
32+
function rvaToOffset(rva: number, sections: Section[]): number | null {
33+
for (const s of sections) {
34+
if (rva >= s.virtualAddress && rva < s.virtualAddress + s.rawSize) {
35+
return s.rawOffset + (rva - s.virtualAddress);
36+
}
37+
}
38+
return null;
39+
}
40+
41+
function parseDllNames(
42+
buf: Uint8Array,
43+
tableRva: number,
44+
sections: Section[],
45+
entrySize: number,
46+
nameRvaOffset: number,
47+
): string[] {
48+
const names: string[] = [];
49+
const tableOffset = rvaToOffset(tableRva, sections);
50+
if (tableOffset === null) return names;
51+
52+
let idx = 0;
53+
while (true) {
54+
const entryOff = tableOffset + idx * entrySize;
55+
const nameRva = readU32(buf, entryOff + nameRvaOffset);
56+
if (nameRva === 0) break;
57+
const nameOff = rvaToOffset(nameRva, sections);
58+
if (nameOff !== null) {
59+
names.push(readCString(buf, nameOff).toLowerCase());
60+
}
61+
idx++;
62+
}
63+
return names;
64+
}
65+
66+
const exePath = Deno.execPath();
67+
const buf = Deno.readFileSync(exePath);
68+
69+
// parse PE
70+
const peOffset = readU32(buf, 0x3c);
71+
const numSections = readU16(buf, peOffset + 6);
72+
const sizeOptHeader = readU16(buf, peOffset + 24 - 4);
73+
const optStart = peOffset + 24;
74+
const magic = readU16(buf, optStart);
75+
76+
if (magic !== 0x20b) {
77+
console.error("not a PE32+ binary");
78+
Deno.exit(1);
79+
}
80+
81+
// data directories start at optStart + 112, preceded by count at +108
82+
const ddStart = optStart + 112;
83+
84+
// read sections
85+
const secStart = optStart + sizeOptHeader;
86+
const sections: Section[] = [];
87+
for (let i = 0; i < numSections; i++) {
88+
const off = secStart + i * 40;
89+
let nameEnd = 8;
90+
while (nameEnd > 0 && buf[off + nameEnd - 1] === 0) nameEnd--;
91+
const name = new TextDecoder().decode(buf.subarray(off, off + nameEnd));
92+
sections.push({
93+
name,
94+
virtualSize: readU32(buf, off + 8),
95+
virtualAddress: readU32(buf, off + 12),
96+
rawSize: readU32(buf, off + 16),
97+
rawOffset: readU32(buf, off + 20),
98+
});
99+
}
100+
101+
// import directory (index 1) - entries are 20 bytes, name RVA at offset 12
102+
const importRva = readU32(buf, ddStart + 1 * 8);
103+
const eagerDlls = parseDllNames(buf, importRva, sections, 20, 12);
104+
105+
// delay import directory (index 13) - entries are 32 bytes, name RVA at offset 4
106+
const delayRva = readU32(buf, ddStart + 13 * 8);
107+
const delayDlls = parseDllNames(buf, delayRva, sections, 32, 4);
108+
109+
// deduplicate and collect all dlls with their load type
110+
const allDlls = new Map<string, string>();
111+
for (const dll of eagerDlls) {
112+
allDlls.set(dll, "eager");
113+
}
114+
for (const dll of delayDlls) {
115+
allDlls.set(dll, "delay");
116+
}
117+
118+
const sorted = [...allDlls.entries()].sort((a, b) => a[0].localeCompare(b[0]));
119+
for (const [dll, type] of sorted) {
120+
console.log(`${dll}: ${type}`);
121+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[# this test is to ensure dlls don't negatively impact windows startup perf. Prefer lazily loading]
2+
advapi32.dll: eager
3+
api-ms-win-core-synch-l1-2-0.dll: eager
4+
bcrypt.dll: eager
5+
bcryptprimitives.dll: eager
6+
combase.dll: delay
7+
crypt32.dll: eager
8+
dbghelp.dll: delay
9+
gdi32.dll: delay
10+
iphlpapi.dll: delay
11+
kernel32.dll: eager
12+
ntdll.dll: eager
13+
oleaut32.dll: delay
14+
opengl32.dll: delay
15+
psapi.dll: delay
16+
setupapi.dll: delay
17+
shell32.dll: eager
18+
user32.dll: eager
19+
userenv.dll: eager
20+
winmm.dll: delay
21+
ws2_32.dll: eager

0 commit comments

Comments
 (0)