Skip to content

Commit

Permalink
Merge pull request #968 from rust-lang/wasm-cdylib
Browse files Browse the repository at this point in the history
Guide the user to using #[crate_type="cdylib"] when using Wasm.
  • Loading branch information
shepmaster authored Sep 7, 2023
2 parents 778f266 + bf603e8 commit 06e2083
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 23 deletions.
17 changes: 17 additions & 0 deletions tests/spec/features/compilation_targets_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@
end

scenario "compiling to WebAssembly" do
editor.set ['#![crate_type = "bin"]', code].join("\n")

in_build_menu { click_on("Wasm") }

within(:output, :code) do
Expand All @@ -115,6 +117,21 @@
end
end

scenario "compiling a library to WebAssembly" do
editor.set <<~EOF
#[no_mangle]
pub fn calculator(a: u8) -> u8 { a + 42 }
EOF

in_build_menu { click_on("Wasm") }

within(:output, :code) do
expect(page).to have_content '(func $calculator (export "calculator")'
end

expect(editor).to have_line('#![crate_type = "cdylib"]')
end

context "when the code doesn't compile" do
before { editor.set("fn main() {") }

Expand Down
1 change: 1 addition & 0 deletions ui/frontend/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ module.exports = {
{
files: [
'.eslintrc.js',
'BuildMenu.tsx',
'PopButton.tsx',
'compileActions.ts',
'editor/AceEditor.tsx',
Expand Down
1 change: 1 addition & 0 deletions ui/frontend/.prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ node_modules

# Slowly migrate files that we've touched
!.eslintrc.js
!BuildMenu.tsx
!PopButton.tsx
!compileActions.ts
!editor/AceEditor.tsx
Expand Down
51 changes: 29 additions & 22 deletions ui/frontend/BuildMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import React, { useCallback } from 'react';
import { useSelector } from 'react-redux';

import * as actions from './actions';
import * as selectors from './selectors';
import { useAppDispatch } from './configureStore';

import ButtonMenuItem from './ButtonMenuItem';
import MenuGroup from './MenuGroup';
import MenuAside from './MenuAside';
import MenuGroup from './MenuGroup';
import * as actions from './actions';
import { useAppDispatch } from './configureStore';
import * as selectors from './selectors';

import styles from './BuildMenu.module.css';

Expand All @@ -18,17 +17,15 @@ interface BuildMenuProps {
const useDispatchAndClose = (action: () => actions.ThunkAction, close: () => void) => {
const dispatch = useAppDispatch();

return useCallback(
() => {
dispatch(action());
close();
},
[action, close, dispatch]
);
}
return useCallback(() => {
dispatch(action());
close();
}, [action, close, dispatch]);
};

const BuildMenu: React.FC<BuildMenuProps> = props => {
const BuildMenu: React.FC<BuildMenuProps> = (props) => {
const isHirAvailable = useSelector(selectors.isHirAvailable);
const wasmLikelyToWork = useSelector(selectors.wasmLikelyToWork);

const compile = useDispatchAndClose(actions.performCompile, props.close);
const compileToAssembly = useDispatchAndClose(actions.performCompileToAssembly, props.close);
Expand All @@ -42,16 +39,13 @@ const BuildMenu: React.FC<BuildMenuProps> = props => {
return (
<MenuGroup title="What do you want to do?">
<ButtonMenuItem name="Run" onClick={execute}>
Build and run the code, showing the output.
Equivalent to <code className={styles.code}>cargo run</code>.
Build and run the code, showing the output. Equivalent to <Code>cargo run</Code>.
</ButtonMenuItem>
<ButtonMenuItem name="Build" onClick={compile}>
Build the code without running it.
Equivalent to <code className={styles.code}>cargo build</code>.
Build the code without running it. Equivalent to <Code>cargo build</Code>.
</ButtonMenuItem>
<ButtonMenuItem name="Test" onClick={test}>
Build the code and run all the tests.
Equivalent to <code className={styles.code}>cargo test</code>.
Build the code and run all the tests. Equivalent to <Code>cargo test</Code>.
</ButtonMenuItem>
<ButtonMenuItem name="ASM" onClick={compileToAssembly}>
Build and show the resulting assembly code.
Expand All @@ -68,15 +62,28 @@ const BuildMenu: React.FC<BuildMenuProps> = props => {
</ButtonMenuItem>
<ButtonMenuItem name="Wasm" onClick={compileToWasm}>
Build a WebAssembly module for web browsers, in the .WAT textual representation.
{!wasmLikelyToWork && <WasmAside />}
</ButtonMenuItem>
</MenuGroup>
);
};

const Code: React.FC<{ children: string }> = ({ children }) => (
<code className={styles.code}>{children}</code>
);

const HirAside: React.FC = () => (
<MenuAside>
Note: HIR currently requires using the Nightly channel, selecting this
option will switch to Nightly.
Note: HIR currently requires using the Nightly channel, selecting this option will switch to
Nightly.
</MenuAside>
);

const WasmAside: React.FC = () => (
<MenuAside>
Note: WebAssembly works best when using the <Code>cdylib</Code> crate type, but the source code
does not specify an explicit crate type. Selecting this option will change the code to specify{' '}
<Code>cdylib</Code>.
</MenuAside>
);

Expand Down
17 changes: 16 additions & 1 deletion ui/frontend/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
clippyRequestSelector,
getCrateType,
runAsTest,
wasmLikelyToWork,
} from './selectors';
import State from './state';
import {
Expand Down Expand Up @@ -89,6 +90,7 @@ export enum ActionType {
EditCode = 'EDIT_CODE',
AddMainFunction = 'ADD_MAIN_FUNCTION',
AddImport = 'ADD_IMPORT',
AddCrateType = 'ADD_CRATE_TYPE',
EnableFeatureGate = 'ENABLE_FEATURE_GATE',
GotoPosition = 'GOTO_POSITION',
SelectText = 'SELECT_TEXT',
Expand Down Expand Up @@ -272,6 +274,15 @@ const performCompileToNightlyHirOnly = (): ThunkAction => dispatch => {
dispatch(performCompileToHirOnly());
};

const performCompileToCdylibWasmOnly = (): ThunkAction => (dispatch, getState) => {
const state = getState();

if (!wasmLikelyToWork(state)) {
dispatch(addCrateType('cdylib'));
}
dispatch(performCompileToWasmOnly());
};

const PRIMARY_ACTIONS: { [index in PrimaryAction]: () => ThunkAction } = {
[PrimaryActionCore.Asm]: performCompileToAssemblyOnly,
[PrimaryActionCore.Compile]: performCompileOnly,
Expand Down Expand Up @@ -310,7 +321,7 @@ export const performCompileToMir =
export const performCompileToNightlyHir =
performAndSwitchPrimaryAction(performCompileToNightlyHirOnly, PrimaryActionCore.Hir);
export const performCompileToWasm =
performAndSwitchPrimaryAction(performCompileToWasmOnly, PrimaryActionCore.Wasm);
performAndSwitchPrimaryAction(performCompileToCdylibWasmOnly, PrimaryActionCore.Wasm);

export const editCode = (code: string) =>
createAction(ActionType.EditCode, { code });
Expand All @@ -321,6 +332,9 @@ export const addMainFunction = () =>
export const addImport = (code: string) =>
createAction(ActionType.AddImport, { code });

export const addCrateType = (crateType: string) =>
createAction(ActionType.AddCrateType, { crateType });

export const enableFeatureGate = (featureGate: string) =>
createAction(ActionType.EnableFeatureGate, { featureGate });

Expand Down Expand Up @@ -617,6 +631,7 @@ export type Action =
| ReturnType<typeof editCode>
| ReturnType<typeof addMainFunction>
| ReturnType<typeof addImport>
| ReturnType<typeof addCrateType>
| ReturnType<typeof enableFeatureGate>
| ReturnType<typeof gotoPosition>
| ReturnType<typeof selectText>
Expand Down
3 changes: 3 additions & 0 deletions ui/frontend/reducers/code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export default function code(state = DEFAULT, action: Action): State {
case ActionType.AddImport:
return action.code + state;

case ActionType.AddCrateType:
return `#![crate_type = "${action.crateType}"]\n${state}`;

case ActionType.EnableFeatureGate:
return `#![feature(${action.featureGate})]\n${state}`;

Expand Down
9 changes: 9 additions & 0 deletions ui/frontend/selectors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,15 @@ export const isNightlyChannel = (state: State) => (
);
export const isHirAvailable = isNightlyChannel;

export const wasmLikelyToWork = createSelector(
crateTypeSelector,
getCrateType, (userCrateType, crateType) => {
// If the user set it already, assume they know what they are doing
if (userCrateType) { return true }

return crateType === 'cdylib';
});

export const getModeLabel = (state: State) => {
const { configuration: { mode } } = state;
return `${mode}`;
Expand Down

0 comments on commit 06e2083

Please sign in to comment.