Skip to content

Commit f8d7de5

Browse files
authored
Merge pull request #1176 from rust-lang/warn-missing-symbols
Warn when assembly or LLVM IR do not appear to generate symbols
2 parents b81f0ea + 7e43f49 commit f8d7de5

File tree

8 files changed

+216
-15
lines changed

8 files changed

+216
-15
lines changed

ui/frontend/.prettierignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,13 @@ node_modules
1919
!Header.tsx
2020
!HelpExample.tsx
2121
!Notifications.tsx
22+
!Output/Assembly.tsx
23+
!Output/LlvmIr.tsx
2224
!Output/OutputPrism.tsx
25+
!Output/PaneWithCode.tsx
2326
!Output/PaneWithMir.tsx
2427
!Output/SimplePane.tsx
28+
!Output/WarnAboutNoSymbols.tsx
2529
!PopButton.tsx
2630
!Prism.tsx
2731
!Stdin.tsx

ui/frontend/Output.tsx

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,17 @@ import { changeFocus } from './reducers/output/meta';
44
import { Focus } from './types';
55

66
import Execute from './Output/Execute';
7+
import Assembly from './Output/Assembly';
78
import Gist from './Output/Gist';
8-
import Section from './Output/Section';
9-
import SimplePane, { SimplePaneProps } from './Output/SimplePane';
9+
import SimplePane from './Output/SimplePane';
1010
import PaneWithMir from './Output/PaneWithMir';
11+
import PaneWithCode from './Output/PaneWithCode';
1112
import * as selectors from './selectors';
1213
import { useAppDispatch, useAppSelector } from './hooks';
1314

1415
import * as styles from './Output.module.css';
1516
import Stdin from './Stdin';
17+
import LlvmIr from './Output/LlvmIr';
1618

1719
const Tab: React.FC<TabProps> = ({ kind, focus, label, onClick, tabProps }) => {
1820
if (selectors.hasProperties(tabProps)) {
@@ -35,16 +37,6 @@ interface TabProps {
3537
tabProps: object;
3638
}
3739

38-
const PaneWithCode: React.FC<PaneWithCodeProps> = ({ code, ...rest }) => (
39-
<SimplePane {...rest}>
40-
<Section kind="code" label="Result">{code}</Section>
41-
</SimplePane>
42-
);
43-
44-
interface PaneWithCodeProps extends SimplePaneProps {
45-
code?: string;
46-
}
47-
4840
const Output: React.FC = () => {
4941
const somethingToShow = useAppSelector(selectors.getSomethingToShow);
5042
const { meta: { focus }, execute, format, clippy, miri, macroExpansion, assembly, llvmIr, mir, hir, wasm, gist } =
@@ -83,8 +75,8 @@ const Output: React.FC = () => {
8375
{focus === Focus.Clippy && <SimplePane {...clippy} kind="clippy" />}
8476
{focus === Focus.Miri && <SimplePane {...miri} kind="miri" />}
8577
{focus === Focus.MacroExpansion && <SimplePane {...macroExpansion} kind="macro-expansion" />}
86-
{focus === Focus.Asm && <PaneWithCode {...assembly} kind="asm" />}
87-
{focus === Focus.LlvmIr && <PaneWithCode {...llvmIr} kind="llvm-ir" />}
78+
{focus === Focus.Asm && <Assembly />}
79+
{focus === Focus.LlvmIr && <LlvmIr />}
8880
{focus === Focus.Mir && <PaneWithMir {...mir} kind="mir" />}
8981
{focus === Focus.Hir && <PaneWithMir {...hir} kind="hir" />}
9082
{focus === Focus.Wasm && <PaneWithCode {...wasm} kind="wasm" />}

ui/frontend/Output/Assembly.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 Assembly: React.FC = () => {
9+
const assembly = useAppSelector((state) => state.output.assembly);
10+
const isAssemblyInProgress = useAppSelector(selectors.isAssemblyInProgressSelector);
11+
const hasAssemblySymbols = useAppSelector(selectors.hasAssemblySymbolsSelector);
12+
13+
return (
14+
<PaneWithCode {...assembly} kind="asm">
15+
<WarnAboutNoSymbols
16+
isInProgress={isAssemblyInProgress}
17+
hasSymbols={hasAssemblySymbols}
18+
name="assembly"
19+
/>
20+
</PaneWithCode>
21+
);
22+
};
23+
24+
export default Assembly;

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: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import React from 'react';
2+
3+
import Section from './Section';
4+
import SimplePane, { SimplePaneProps } from './SimplePane';
5+
6+
export interface PaneWithCodeProps extends SimplePaneProps {
7+
code?: string;
8+
}
9+
10+
const PaneWithCode: React.FC<PaneWithCodeProps> = ({ children, code, ...rest }) => (
11+
<SimplePane {...rest}>
12+
<Section kind="code" label="Result">
13+
{code}
14+
</Section>
15+
{children}
16+
</SimplePane>
17+
);
18+
19+
export default PaneWithCode;
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: 80 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 { 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

@@ -64,3 +68,78 @@ describe('checking for a main function', () => {
6468
expect(doMainFunctionSelector('fn main(/* comment */) {')).toBe(true);
6569
});
6670
});
71+
72+
const doHasAssemblySymbolSelector = (code: string) => {
73+
const state = reducer(
74+
{ output: { assembly: { code, requestsInProgress: 0 } } },
75+
{ type: 'test' },
76+
);
77+
return hasAssemblySymbolsSelector(state);
78+
};
79+
80+
describe('checking for symbols in assembly output', () => {
81+
test('empty code has no symbols', () => {
82+
expect(doHasAssemblySymbolSelector('')).toBe(false);
83+
});
84+
85+
test('instructions are not symbols', () => {
86+
// x86_64
87+
expect(doHasAssemblySymbolSelector(' movl %edi, 4(%rsp)')).toBe(false);
88+
// arm
89+
expect(doHasAssemblySymbolSelector(' sub sp, sp, #32')).toBe(false);
90+
});
91+
92+
test('mangled symbols are symbols', () => {
93+
expect(doHasAssemblySymbolSelector('_ZN10playground3add17h903bea7e047dfb9fE:')).toBe(true);
94+
});
95+
96+
test('unmangled symbols are symbols', () => {
97+
expect(doHasAssemblySymbolSelector('playground::add:')).toBe(true);
98+
});
99+
100+
test('unmangled symbols from traits are symbols', () => {
101+
expect(
102+
doHasAssemblySymbolSelector(
103+
'<rand::rngs::reseeding::ReseedingCore<R,Rsdr> as rand_core::block::BlockRngCore>::generate:',
104+
),
105+
).toBe(true);
106+
});
107+
108+
test('symbols with comments are symbols', () => {
109+
// x86_64
110+
expect(doHasAssemblySymbolSelector('add: # @add')).toBe(
111+
true,
112+
);
113+
// arm
114+
expect(doHasAssemblySymbolSelector('add: // @add')).toBe(
115+
true,
116+
);
117+
});
118+
});
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: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,30 @@ export const compileRequestPayloadSelector = createSelector(
518518
}),
519519
);
520520

521+
export const isAssemblyInProgressSelector = createSelector(
522+
(state: State) => state.output.assembly,
523+
asm => asm.requestsInProgress > 0,
524+
);
525+
526+
const ASSEMBLY_SYMBOLS_RE = /^[_a-zA-Z0-9<>, ]+:/m;
527+
528+
export const hasAssemblySymbolsSelector = createSelector(
529+
(state: State) => state.output.assembly,
530+
asm => !!asm.code?.match(ASSEMBLY_SYMBOLS_RE),
531+
);
532+
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+
521545
export const themeSelector = createSelector(
522546
(state: State) => state,
523547
(state) => state.configuration.theme,

0 commit comments

Comments
 (0)