Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ui/frontend/.prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@ node_modules
!Header.tsx
!HelpExample.tsx
!Notifications.tsx
!Output/Assembly.tsx
!Output/LlvmIr.tsx
!Output/OutputPrism.tsx
!Output/PaneWithCode.tsx
!Output/PaneWithMir.tsx
!Output/SimplePane.tsx
!Output/WarnAboutNoSymbols.tsx
!PopButton.tsx
!Prism.tsx
!Stdin.tsx
Expand Down
20 changes: 6 additions & 14 deletions ui/frontend/Output.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@ import { changeFocus } from './reducers/output/meta';
import { Focus } from './types';

import Execute from './Output/Execute';
import Assembly from './Output/Assembly';
import Gist from './Output/Gist';
import Section from './Output/Section';
import SimplePane, { SimplePaneProps } from './Output/SimplePane';
import SimplePane from './Output/SimplePane';
import PaneWithMir from './Output/PaneWithMir';
import PaneWithCode from './Output/PaneWithCode';
import * as selectors from './selectors';
import { useAppDispatch, useAppSelector } from './hooks';

import * as styles from './Output.module.css';
import Stdin from './Stdin';
import LlvmIr from './Output/LlvmIr';

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

const PaneWithCode: React.FC<PaneWithCodeProps> = ({ code, ...rest }) => (
<SimplePane {...rest}>
<Section kind="code" label="Result">{code}</Section>
</SimplePane>
);

interface PaneWithCodeProps extends SimplePaneProps {
code?: string;
}

const Output: React.FC = () => {
const somethingToShow = useAppSelector(selectors.getSomethingToShow);
const { meta: { focus }, execute, format, clippy, miri, macroExpansion, assembly, llvmIr, mir, hir, wasm, gist } =
Expand Down Expand Up @@ -83,8 +75,8 @@ const Output: React.FC = () => {
{focus === Focus.Clippy && <SimplePane {...clippy} kind="clippy" />}
{focus === Focus.Miri && <SimplePane {...miri} kind="miri" />}
{focus === Focus.MacroExpansion && <SimplePane {...macroExpansion} kind="macro-expansion" />}
{focus === Focus.Asm && <PaneWithCode {...assembly} kind="asm" />}
{focus === Focus.LlvmIr && <PaneWithCode {...llvmIr} kind="llvm-ir" />}
{focus === Focus.Asm && <Assembly />}
{focus === Focus.LlvmIr && <LlvmIr />}
{focus === Focus.Mir && <PaneWithMir {...mir} kind="mir" />}
{focus === Focus.Hir && <PaneWithMir {...hir} kind="hir" />}
{focus === Focus.Wasm && <PaneWithCode {...wasm} kind="wasm" />}
Expand Down
24 changes: 24 additions & 0 deletions ui/frontend/Output/Assembly.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';

import { useAppSelector } from '../hooks';
import * as selectors from '../selectors';
import PaneWithCode from './PaneWithCode';
import WarnAboutNoSymbols from './WarnAboutNoSymbols';

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

return (
<PaneWithCode {...assembly} kind="asm">
<WarnAboutNoSymbols
isInProgress={isAssemblyInProgress}
hasSymbols={hasAssemblySymbols}
name="assembly"
/>
</PaneWithCode>
);
};

export default Assembly;
24 changes: 24 additions & 0 deletions ui/frontend/Output/LlvmIr.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';

import { useAppSelector } from '../hooks';
import * as selectors from '../selectors';
import PaneWithCode from './PaneWithCode';
import WarnAboutNoSymbols from './WarnAboutNoSymbols';

const LlvmIr: React.FC = () => {
const llvmIr = useAppSelector((state) => state.output.llvmIr);
const isLlvmIrInProgress = useAppSelector(selectors.isLlvmIrInProgressSelector);
const hasLlvmIrSymbols = useAppSelector(selectors.hasLlvmIrSymbolsSelector);

return (
<PaneWithCode {...llvmIr} kind="llvm-ir">
<WarnAboutNoSymbols
isInProgress={isLlvmIrInProgress}
hasSymbols={hasLlvmIrSymbols}
name="LLVM IR"
/>
</PaneWithCode>
);
};

export default LlvmIr;
19 changes: 19 additions & 0 deletions ui/frontend/Output/PaneWithCode.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';

import Section from './Section';
import SimplePane, { SimplePaneProps } from './SimplePane';

export interface PaneWithCodeProps extends SimplePaneProps {
code?: string;
}

const PaneWithCode: React.FC<PaneWithCodeProps> = ({ children, code, ...rest }) => (
<SimplePane {...rest}>
<Section kind="code" label="Result">
{code}
</Section>
{children}
</SimplePane>
);

export default PaneWithCode;
35 changes: 35 additions & 0 deletions ui/frontend/Output/WarnAboutNoSymbols.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react';

import Section from './Section';

export interface WarnAboutNoSymbolsProps {
isInProgress: boolean;
hasSymbols: boolean;
name: string;
}

const WarnAboutNoSymbols: React.FC<WarnAboutNoSymbolsProps> = ({
isInProgress,
hasSymbols,
name,
}) => {
const warnAboutNoSymbols = !isInProgress && !hasSymbols;

if (!warnAboutNoSymbols) {
return null;
}

return (
<Section kind="warning" label="Warnings">
No symbols detected — they may have been optimized away.
{'\n'}
Add the <code>#[unsafe(no_mangle)]</code> attribute to
{'\n'}
functions you want to see {name} for. Generic functions
{'\n'}
only generate {name} when concrete types are provided.
</Section>
);
};

export default WarnAboutNoSymbols;
81 changes: 80 additions & 1 deletion ui/frontend/selectors/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import reducer from '../reducers';
import { editCode } from '../reducers/code';
import { hasMainFunctionSelector } from './index';
import {
hasAssemblySymbolsSelector,
hasLlvmIrSymbolsSelector,
hasMainFunctionSelector,
} from './index';

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

Expand Down Expand Up @@ -64,3 +68,78 @@ describe('checking for a main function', () => {
expect(doMainFunctionSelector('fn main(/* comment */) {')).toBe(true);
});
});

const doHasAssemblySymbolSelector = (code: string) => {
const state = reducer(
{ output: { assembly: { code, requestsInProgress: 0 } } },
{ type: 'test' },
);
return hasAssemblySymbolsSelector(state);
};

describe('checking for symbols in assembly output', () => {
test('empty code has no symbols', () => {
expect(doHasAssemblySymbolSelector('')).toBe(false);
});

test('instructions are not symbols', () => {
// x86_64
expect(doHasAssemblySymbolSelector(' movl %edi, 4(%rsp)')).toBe(false);
// arm
expect(doHasAssemblySymbolSelector(' sub sp, sp, #32')).toBe(false);
});

test('mangled symbols are symbols', () => {
expect(doHasAssemblySymbolSelector('_ZN10playground3add17h903bea7e047dfb9fE:')).toBe(true);
});

test('unmangled symbols are symbols', () => {
expect(doHasAssemblySymbolSelector('playground::add:')).toBe(true);
});

test('unmangled symbols from traits are symbols', () => {
expect(
doHasAssemblySymbolSelector(
'<rand::rngs::reseeding::ReseedingCore<R,Rsdr> as rand_core::block::BlockRngCore>::generate:',
),
).toBe(true);
});

test('symbols with comments are symbols', () => {
// x86_64
expect(doHasAssemblySymbolSelector('add: # @add')).toBe(
true,
);
// arm
expect(doHasAssemblySymbolSelector('add: // @add')).toBe(
true,
);
});
});

const doHasLlvmIrSymbolsSelector = (code: string) => {
const state = reducer({ output: { llvmIr: { code, requestsInProgress: 0 } } }, { type: 'test' });
return hasLlvmIrSymbolsSelector(state);
};

describe('checking for symbols in LLVM IR output', () => {
test('empty code has no symbols', () => {
expect(doHasLlvmIrSymbolsSelector('')).toBe(false);
});

test('metadata is not a symbol', () => {
expect(
doHasLlvmIrSymbolsSelector('source_filename = "playground.d1ee58e2761c15fe-cgu.0"'),
).toBe(false);
expect(doHasLlvmIrSymbolsSelector('!llvm.ident = !{!1}')).toBe(false);
expect(
doHasLlvmIrSymbolsSelector('!1 = !{!"rustc version 1.90.0-nightly (3048886e5 2025-07-30)"}'),
).toBe(false);
});

test('a symbol is a symbol', () => {
expect(
doHasLlvmIrSymbolsSelector('define noundef i32 @add(i32 noundef %v) unnamed_addr #0 {'),
).toBe(true);
});
});
24 changes: 24 additions & 0 deletions ui/frontend/selectors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,30 @@ export const compileRequestPayloadSelector = createSelector(
}),
);

export const isAssemblyInProgressSelector = createSelector(
(state: State) => state.output.assembly,
asm => asm.requestsInProgress > 0,
);

const ASSEMBLY_SYMBOLS_RE = /^[_a-zA-Z0-9<>, ]+:/m;

export const hasAssemblySymbolsSelector = createSelector(
(state: State) => state.output.assembly,
asm => !!asm.code?.match(ASSEMBLY_SYMBOLS_RE),
);

export const isLlvmIrInProgressSelector = createSelector(
(state: State) => state.output.llvmIr,
llvmIr => llvmIr.requestsInProgress > 0,
);

const LLVMIR_SYMBOLS_RE = /^define.*@.*{/m;

export const hasLlvmIrSymbolsSelector = createSelector(
(state: State) => state.output.llvmIr,
llvmIr => !!llvmIr.code?.match(LLVMIR_SYMBOLS_RE),
);

export const themeSelector = createSelector(
(state: State) => state,
(state) => state.configuration.theme,
Expand Down