Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

naked functions #1689

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 5 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
2 changes: 2 additions & 0 deletions src/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ The following is an index of all built-in attributes.
- Code generation
- [`inline`] --- Hint to inline code.
- [`cold`] --- Hint that a function is unlikely to be called.
- [`naked`] - Prevent the compiler from emitting a function prologue.
- [`no_builtins`] --- Disables use of certain built-in functions.
- [`target_feature`] --- Configure platform-specific code generation.
- [`track_caller`] --- Pass the parent call location to `std::panic::Location::caller()`.
Expand Down Expand Up @@ -367,6 +368,7 @@ The following is an index of all built-in attributes.
[`macro_export`]: macros-by-example.md#path-based-scope
[`macro_use`]: macros-by-example.md#the-macro_use-attribute
[`must_use`]: attributes/diagnostics.md#the-must_use-attribute
[`naked`]: attributes/codegen.md#the-naked-attribute
[`no_builtins`]: attributes/codegen.md#the-no_builtins-attribute
[`no_implicit_prelude`]: names/preludes.md#the-no_implicit_prelude-attribute
[`no_link`]: items/extern-crates.md#the-no_link-attribute
Expand Down
57 changes: 56 additions & 1 deletion src/attributes/codegen.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,53 @@ r[attributes.codegen.cold]
The *`cold` [attribute]* suggests that the attributed function is unlikely to
be called.

## The `naked` attribute

r[attributes.codegen.naked]

r[attributes.codegen.naked.intro]
The *`naked` [attribute]* prevents the compiler from emitting a function prologue and
epilogue for the attributed function.

r[attributes.codegen.naked.body]
The [function body] must consist of exactly one [`naked_asm!`] macro invocation, which
may be enclosed within an [unsafe block].

r[attributes.codegen.naked.prologue-epilogue]
No function prologue or epilogue are generated for the attributed function: the contents
of the `naked_asm!` invocation make up the full body of a naked function.

r[attributes.codegen.naked.call-stack]
The caller must set up the call stack according to the specified calling convention before
executing a naked function, even in contexts where setting up the call stack would ordinarily
be unnecessary, such as when the function is inlined.

An implementation can fulfill this requirement by guaranteeing that naked functions
are never inlined. However, implementations are not currently required to guarantee that
naked functions are never inlined.

In the future it may become a requirement for implementations to guarantee that
naked functions are never inlined; users must not rely on any observable behavior
that may result from inlining. according to the specified calling convention before
executing a naked function,
folkertdev marked this conversation as resolved.
Show resolved Hide resolved

r[attributes.codegen.naked.unsafe-function]
A naked function that makes use of registers in a way that does not conform
to the specified calling convention imposes additional safety invariants on its caller,
and therefore must be marked as an [unsafe function].

r[attributes.codegen.naked.unused-variables]
The [`unused_variables`] lint is suppressed within naked functions.

r[attributes.codegen.naked.inline]
A naked function cannot be attributed by the [`inline`](#the-inline-attribute) attribute.

r[attributes.codegen.naked.track_caller]
A naked function cannot be attributed by the [`track_caller`](#the-track_caller-attribute) attribute.

r[attributes.codegen.naked.testing]
A naked function cannot be attributed by [the testing attributes](../testing.md).

## The `no_builtins` attribute

r[attributes.codegen.no_builtins]
Expand Down Expand Up @@ -469,14 +516,22 @@ trait object whose methods are attributed.
[`-C target-feature`]: ../../rustc/codegen-options/index.html#target-feature
[`is_x86_feature_detected`]: ../../std/arch/macro.is_x86_feature_detected.html
[`is_aarch64_feature_detected`]: ../../std/arch/macro.is_aarch64_feature_detected.html
[`naked_asm!`]: ../inline-assembly.md
[`inline`]: #the-inline-attribute
[`track_caller`]: #the-track-caller-attribute
[`target_feature` conditional compilation option]: ../conditional-compilation.md#target_feature
[`unused_variables`]: ../../rustc/lints/listing/warn-by-default.html#unused-variables
[attribute]: ../attributes.md
[attributes]: ../attributes.md
[FFI-safe]: ../../rustc/lints/listing/warn-by-default.html#improper-ctypes-definitions
[function body]: ../items/functions.md#function-body
[functions]: ../items/functions.md
[rules for inline assembly]: ../inline-assembly.md#rules-for-inline-assembly
[target architecture]: ../conditional-compilation.md#target_arch
[trait]: ../items/traits.md
[undefined behavior]: ../behavior-considered-undefined.md
[unsafe function]: ../unsafe-keyword.md
[unsafe block]: ../unsafe-blocks.md
[unsafe function]: ../unsafe-functions.md
[rust-abi]: ../items/external-blocks.md#abi
[`Location`]: core::panic::Location

Expand Down
59 changes: 55 additions & 4 deletions src/inline-assembly.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
r[asm]

r[asm.intro]
Support for inline assembly is provided via the [`asm!`] and [`global_asm!`] macros.
Support for inline assembly is provided via the [`asm!`], [`naked_asm!`] and [`global_asm!`] macros.
It can be used to embed handwritten assembly in the assembly output generated by the compiler.

[`asm!`]: core::arch::asm
[`naked_asm!`]: core::arch::naked_asm
[`global_asm!`]: core::arch::global_asm

r[asm.stable-targets]
Expand Down Expand Up @@ -61,6 +62,7 @@ option := "pure" / "nomem" / "readonly" / "preserves_flags" / "noreturn" / "nost
options := "options(" option *("," option) [","] ")"
operand := reg_operand / clobber_abi / options
asm := "asm!(" format_string *("," format_string) *("," operand) [","] ")"
naked_asm := "asm!(" format_string *("," format_string) *("," operand) [","] ")"
global_asm := "global_asm!(" format_string *("," format_string) *("," operand) [","] ")"
```

Expand All @@ -69,13 +71,17 @@ global_asm := "global_asm!(" format_string *("," format_string) *("," operand) [
r[asm.scope]

r[asm.scope.intro]
Inline assembly can be used in one of two ways.
Inline assembly can be used in one of three ways.

r[asm.scope.asm]
With the `asm!` macro, the assembly code is emitted in a function scope and integrated into the compiler-generated assembly code of a function.
This assembly code must obey [strict rules](#rules-for-inline-assembly) to avoid undefined behavior.
Note that in some cases the compiler may choose to emit the assembly code as a separate function and generate a call to it.

r[asm.scope.naked_asm]
With the `naked_asm!` macro, the assembly code is emitted in a function scope and constitutes the full assembly code of a function.
The `naked_asm!` macro is only allowed in [naked functions](../attributes/codegen.md#the-naked-attribute).

r[asm.scope.global_asm]
With the `global_asm!` macro, the assembly code is emitted in a global scope, outside a function.
This can be used to hand-write entire functions using assembly code, and generally provides much more freedom to use arbitrary registers and assembler directives.
Expand Down Expand Up @@ -185,8 +191,11 @@ Operand expressions are evaluated from left to right, just like function call ar
After the `asm!` has executed, outputs are written to in left to right order.
This is significant if two outputs point to the same place: that place will contain the value of the rightmost output.

r[asm.operand-type.naked_asm-restriction]
Because `naked_asm!` defines a whole function body, it can only use `sym` and `const` operands.

r[asm.operand-type.global_asm-restriction]
Since `global_asm!` exists outside a function, it can only use `sym` and `const` operands.
Because `global_asm!` exists outside a function, it can only use `sym` and `const` operands.

## Register operands

Expand Down Expand Up @@ -578,9 +587,13 @@ r[asm.options.checks.pure]
r[asm.options.checks.noreturn]
- It is a compile-time error to specify `noreturn` on an asm block with outputs.

r[asm.options.naked_asm-restriction]
`global_asm!` only supports the `att_syntax` and `raw` options.
folkertdev marked this conversation as resolved.
Show resolved Hide resolved
The remaining options are not meaningful because the inline assembly defines the whole function body.

r[asm.options.global_asm-restriction]
`global_asm!` only supports the `att_syntax` and `raw` options.
The remaining options are not meaningful for global-scope inline assembly
The remaining options are not meaningful for global-scope inline assembly.

## Rules for inline assembly

Expand Down Expand Up @@ -693,6 +706,44 @@ r[asm.rules.x86-prefix-restriction]
r[asm.rules.preserves_flags]
> **Note**: As a general rule, the flags covered by `preserves_flags` are those which are *not* preserved when performing a function call.

## Rules for naked inline assembly

r[asm.naked-rules]

r[asm.naked-rules.intro]
To avoid undefined behavior, these rules must be followed when using function-scope inline assembly in naked functions (`naked_asm!`):

r[asm.naked-rules.reg-not-input]
- Any registers not used for function inputs according to the calling convention and function signature will contain an undefined value on entry to the asm block.
- An "undefined value" in the context of inline assembly means that the register can (non-deterministically) have any one of the possible values allowed by the architecture.
Notably it is not the same as an LLVM `undef` which can have a different value every time you read it (since such a concept does not exist in assembly code).

r[asm.naked-rules.reg-not-output]
- Any callee-saved registers must have the same value upon exiting the asm block as they had on entry, otherwise behavior is undefined.
folkertdev marked this conversation as resolved.
Show resolved Hide resolved
- Caller-saved registes may be used freely, even if they are not used for the return value.

r[asm.naked-rules.unwind]
- Behavior is undefined if execution unwinds out of an asm block.
- This also applies if the assembly code calls a function which then unwinds.

r[asm.naked-rules.noreturn]
- Behavior is undefined if execution falls through to the end of the asm block.
folkertdev marked this conversation as resolved.
Show resolved Hide resolved

r[asm.naked-rules.mem-same-as-ffi]
- The set of memory locations that assembly code is allowed to read and write are the same as those allowed for an FFI function.
- Refer to the unsafe code guidelines for the exact rules.
- These rules do not apply to memory which is private to the asm code, such as stack space allocated within the asm block.

r[asm.naked-rules.black-box]
- The compiler cannot assume that the instructions in the asm are the ones that will actually end up executed.
- This effectively means that the compiler must treat the `naked_asm!` as a black box and only take the interface specification into account, not the instructions themselves.
- Runtime code patching is allowed, via target-specific mechanisms.
- However there is no guarantee that each `naked_asm!` directly corresponds to a single instance of instructions in the object file: the compiler is free to duplicate or deduplicate `naked_asm!` blocks.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not true, naked functions cannot be duplicated/merged, unlike asm!.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and reading this over, in this case it seems important that we do actually guarantee (and hence the compiler can assume) that the instructions in the naked_asm! invocation are exactly the ones that will be executed. or is that too strict?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that poses problems on SPIR-V or NVPTX targets. I seem to recall that the loader just inlines the functions at the Shader Assembly level.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chorman0773 just to be sure, you're referring to Amanieu's comment right?

What we do in rustc (or well, we will once that PR makes it through the queue) is that a naked function

#[naked]
extern "C" fn foo() {}

is emitted as something similar to

core::arch::global_asm!( 
    "foo:",
    "ret"
);

extern "C" {
    fn foo();
}

and we're relying on the compiler (rustc, llvm) not duplicating that bit of global assembly. I suspect we already rely on that in general, because global assembly can define symbols today.

If you still think this is problematic, how could we verify the behavior of this new codegen strategy for the targets you list?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and reading this over, in this case it seems important that we do actually guarantee (and hence the compiler can assume) that the instructions in the naked_asm! invocation are exactly the ones that will be executed. or is that too strict?

No, they may not be the same instructions that are executed since you can still do runtime patching. We only guarantee that assembly code only appears once in the object file for the purposes of symbols.


r[asm.naked-rules.not-exactly-once]
- You cannot assume that an `naked_asm!` block will appear exactly once in the output binary.
The compiler is allowed to instantiate multiple copies of the `naked_asm!` block, for example when the function containing it is inlined in multiple places.
folkertdev marked this conversation as resolved.
Show resolved Hide resolved

### Correctness and Validity

r[asm.validity]
Expand Down
Loading