Skip to content

Commit 7e43f49

Browse files
committed
Warn when LLVM IR doesn't appear to generate symbols
1 parent 02acf9d commit 7e43f49

File tree

7 files changed

+113
-16
lines changed

7 files changed

+113
-16
lines changed

ui/frontend/.prettierignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@ node_modules
2020
!HelpExample.tsx
2121
!Notifications.tsx
2222
!Output/Assembly.tsx
23+
!Output/LlvmIr.tsx
2324
!Output/OutputPrism.tsx
2425
!Output/PaneWithCode.tsx
2526
!Output/PaneWithMir.tsx
2627
!Output/SimplePane.tsx
28+
!Output/WarnAboutNoSymbols.tsx
2729
!PopButton.tsx
2830
!Prism.tsx
2931
!Stdin.tsx

ui/frontend/Output.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { useAppDispatch, useAppSelector } from './hooks';
1414

1515
import * as styles from './Output.module.css';
1616
import Stdin from './Stdin';
17+
import LlvmIr from './Output/LlvmIr';
1718

1819
const Tab: React.FC<TabProps> = ({ kind, focus, label, onClick, tabProps }) => {
1920
if (selectors.hasProperties(tabProps)) {
@@ -75,7 +76,7 @@ const Output: React.FC = () => {
7576
{focus === Focus.Miri && <SimplePane {...miri} kind="miri" />}
7677
{focus === Focus.MacroExpansion && <SimplePane {...macroExpansion} kind="macro-expansion" />}
7778
{focus === Focus.Asm && <Assembly />}
78-
{focus === Focus.LlvmIr && <PaneWithCode {...llvmIr} kind="llvm-ir" />}
79+
{focus === Focus.LlvmIr && <LlvmIr />}
7980
{focus === Focus.Mir && <PaneWithMir {...mir} kind="mir" />}
8081
{focus === Focus.Hir && <PaneWithMir {...hir} kind="hir" />}
8182
{focus === Focus.Wasm && <PaneWithCode {...wasm} kind="wasm" />}

ui/frontend/Output/Assembly.tsx

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,20 @@ import React from 'react';
33
import { useAppSelector } from '../hooks';
44
import * as selectors from '../selectors';
55
import PaneWithCode from './PaneWithCode';
6-
import Section from './Section';
6+
import WarnAboutNoSymbols from './WarnAboutNoSymbols';
77

88
const Assembly: React.FC = () => {
99
const assembly = useAppSelector((state) => state.output.assembly);
1010
const isAssemblyInProgress = useAppSelector(selectors.isAssemblyInProgressSelector);
1111
const hasAssemblySymbols = useAppSelector(selectors.hasAssemblySymbolsSelector);
1212

13-
const warnAboutNoSymbols = !isAssemblyInProgress && !hasAssemblySymbols;
14-
1513
return (
1614
<PaneWithCode {...assembly} kind="asm">
17-
{warnAboutNoSymbols ? (
18-
<Section kind="warning" label="Warnings">
19-
No symbols detected — they may have been optimized away.
20-
{'\n'}
21-
Add the <code>#[unsafe(no_mangle)]</code> attribute to
22-
{'\n'}
23-
functions you want to see assembly for. Generic functions
24-
{'\n'}
25-
only generate assembly when concrete types are provided.
26-
</Section>
27-
) : null}
15+
<WarnAboutNoSymbols
16+
isInProgress={isAssemblyInProgress}
17+
hasSymbols={hasAssemblySymbols}
18+
name="assembly"
19+
/>
2820
</PaneWithCode>
2921
);
3022
};

ui/frontend/Output/LlvmIr.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import React from 'react';
2+
3+
import { useAppSelector } from '../hooks';
4+
import * as selectors from '../selectors';
5+
import PaneWithCode from './PaneWithCode';
6+
import WarnAboutNoSymbols from './WarnAboutNoSymbols';
7+
8+
const LlvmIr: React.FC = () => {
9+
const llvmIr = useAppSelector((state) => state.output.llvmIr);
10+
const isLlvmIrInProgress = useAppSelector(selectors.isLlvmIrInProgressSelector);
11+
const hasLlvmIrSymbols = useAppSelector(selectors.hasLlvmIrSymbolsSelector);
12+
13+
return (
14+
<PaneWithCode {...llvmIr} kind="llvm-ir">
15+
<WarnAboutNoSymbols
16+
isInProgress={isLlvmIrInProgress}
17+
hasSymbols={hasLlvmIrSymbols}
18+
name="LLVM IR"
19+
/>
20+
</PaneWithCode>
21+
);
22+
};
23+
24+
export default LlvmIr;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React from 'react';
2+
3+
import Section from './Section';
4+
5+
export interface WarnAboutNoSymbolsProps {
6+
isInProgress: boolean;
7+
hasSymbols: boolean;
8+
name: string;
9+
}
10+
11+
const WarnAboutNoSymbols: React.FC<WarnAboutNoSymbolsProps> = ({
12+
isInProgress,
13+
hasSymbols,
14+
name,
15+
}) => {
16+
const warnAboutNoSymbols = !isInProgress && !hasSymbols;
17+
18+
if (!warnAboutNoSymbols) {
19+
return null;
20+
}
21+
22+
return (
23+
<Section kind="warning" label="Warnings">
24+
No symbols detected — they may have been optimized away.
25+
{'\n'}
26+
Add the <code>#[unsafe(no_mangle)]</code> attribute to
27+
{'\n'}
28+
functions you want to see {name} for. Generic functions
29+
{'\n'}
30+
only generate {name} when concrete types are provided.
31+
</Section>
32+
);
33+
};
34+
35+
export default WarnAboutNoSymbols;

ui/frontend/selectors/index.spec.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import reducer from '../reducers';
22
import { editCode } from '../reducers/code';
3-
import { hasAssemblySymbolsSelector, hasMainFunctionSelector } from './index';
3+
import {
4+
hasAssemblySymbolsSelector,
5+
hasLlvmIrSymbolsSelector,
6+
hasMainFunctionSelector,
7+
} from './index';
48

59
const buildState = (code: string) => reducer(undefined, editCode(code));
610

@@ -112,3 +116,30 @@ describe('checking for symbols in assembly output', () => {
112116
);
113117
});
114118
});
119+
120+
const doHasLlvmIrSymbolsSelector = (code: string) => {
121+
const state = reducer({ output: { llvmIr: { code, requestsInProgress: 0 } } }, { type: 'test' });
122+
return hasLlvmIrSymbolsSelector(state);
123+
};
124+
125+
describe('checking for symbols in LLVM IR output', () => {
126+
test('empty code has no symbols', () => {
127+
expect(doHasLlvmIrSymbolsSelector('')).toBe(false);
128+
});
129+
130+
test('metadata is not a symbol', () => {
131+
expect(
132+
doHasLlvmIrSymbolsSelector('source_filename = "playground.d1ee58e2761c15fe-cgu.0"'),
133+
).toBe(false);
134+
expect(doHasLlvmIrSymbolsSelector('!llvm.ident = !{!1}')).toBe(false);
135+
expect(
136+
doHasLlvmIrSymbolsSelector('!1 = !{!"rustc version 1.90.0-nightly (3048886e5 2025-07-30)"}'),
137+
).toBe(false);
138+
});
139+
140+
test('a symbol is a symbol', () => {
141+
expect(
142+
doHasLlvmIrSymbolsSelector('define noundef i32 @add(i32 noundef %v) unnamed_addr #0 {'),
143+
).toBe(true);
144+
});
145+
});

ui/frontend/selectors/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,18 @@ export const hasAssemblySymbolsSelector = createSelector(
530530
asm => !!asm.code?.match(ASSEMBLY_SYMBOLS_RE),
531531
);
532532

533+
export const isLlvmIrInProgressSelector = createSelector(
534+
(state: State) => state.output.llvmIr,
535+
llvmIr => llvmIr.requestsInProgress > 0,
536+
);
537+
538+
const LLVMIR_SYMBOLS_RE = /^define.*@.*{/m;
539+
540+
export const hasLlvmIrSymbolsSelector = createSelector(
541+
(state: State) => state.output.llvmIr,
542+
llvmIr => !!llvmIr.code?.match(LLVMIR_SYMBOLS_RE),
543+
);
544+
533545
export const themeSelector = createSelector(
534546
(state: State) => state,
535547
(state) => state.configuration.theme,

0 commit comments

Comments
 (0)