You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: text/0000-isa-attribute.md
+85-9
Original file line number
Diff line number
Diff line change
@@ -11,9 +11,7 @@ This RFC proposes a new function attribute, `#[instruction_set(arch, set)]` whic
11
11
# Motivation
12
12
[motivation]: #motivation
13
13
14
-
Most programmers are familiar with the idea of a CPU family having more than one instruction set. `x86_64` is backwards compatible with `x86`, and an `x86_64` CPU can run an `x86` program if necessary.
15
-
16
-
Starting with `ARMv4T`, many ARM CPUs support two separate instruction sets. At the time they were called "ARM code" and "Thumb code", but with the development of `AArch64`, they're now called `a32` and `t32`. Unlike with the `x86` / `x86_64` situation, on ARM you can have a single program that intersperses both `a32` and `t32` code. A particular form of branch instruction allows for the CPU to change between the two modes any time it branches, and so generally code is designated as being either `a32` or `t32` on a per-function basis.
14
+
Starting with `ARMv4T`, many ARM CPUs support two separate instruction sets. At the time they were called "ARM code" and "Thumb code", but with the development of `AArch64`, they're now called `a32` and `t32`. Unlike with the `x86_64` architecture, where the CPU can run both `x86` and `x86_64` code, but a single program still uses just one of the two instruction sets, on ARM you can have a single program that intersperses both `a32` and `t32` code. A particular form of branch instruction allows for the CPU to change between the two modes any time it branches, and so generally code is designated as being either `a32` or `t32` on a per-function basis.
17
15
18
16
In LLVM, selecting that code should be `a32` or `t32` is done by either disabling (for `a32`) or enabling (for `t32`) the `thumb-mode` target feature. Previously, Rust was able to do this using the `target_feature` attribute because it was able to either add _or subtract_ an LLVM target feature during a function. However, when [RFC 2045](https://github.com/rust-lang/rfcs/blob/master/text/2045-target-feature.md) was accepted, its final form did not allow for the subtraction of target features. Its final form is primarily designed around always opting _in_ to additional features, and it's no longer the correct tool for an "either A or B, but not both" situation like `a32`/`t32` is.
19
17
@@ -64,25 +62,33 @@ Every target is now considered to have one default instruction set (for function
64
62
Backend support:
65
63
* In LLVM this corresponds to enabling or disabling the `thumb-mode` target feature on a function.
66
64
* Other future backends (eg: Cranelift) would presumably support this in some similar way. A "quick and dirty" version of `a32`/`t32` interworking can be achieved simply by simply placing all `a32` code in one translation unit, all `t32` code in another, and then telling the linker to sort it out. Currently, Cranelift does not support ARM chips _at all_, but they can easily work towards this over time.
65
+
* Because Miri operates on Rust's MIR stage, this attribute doesn't affect the operation of Miri. If Miri were to some day support inline assembly this attribute would need to be taken into account for that to work right, but Miri could also simply choose to not support this attribute in combination with inline assembly.
67
66
68
67
Guarantees:
69
68
* If an alternate instruction set is designated on a function then the compiler _must_ respect that. It is not a hint, it is a guarantee.
70
69
70
+
Where can this attribute be used:
71
+
* This attribute can be used on any `fn` item that has a body: Free functions, inherent methods, trait default methods, and trait impl methods.
72
+
* This attribute cannot be used on closures or within `extern` block declarations.
73
+
* (Allowing this on trait prototypes is a Future Possibility.)
74
+
71
75
What is a Compile Error:
72
-
* If an alternate instruction set is designated that doesn't exist (eg: "unicorn") then that is a compiler error.
76
+
* If an alternate instruction set is designated that doesn't exist (eg: "unicorn") then that is a compiler error. Later versions of the compiler/language are free to add additional arch/instruction set pairs.
73
77
* If the attribute appears more than once for a _single arch_ on a function that is a compile error.
74
78
* Specifying an alternate instruction set attribute more than once with each usage being for a _different arch_ it is allowed.
75
79
76
80
Inlining:
77
81
* For the alternate instruction sets proposed by this RFC, `a32` and `t32`, what is affected is the actual generated assembly and symbol placement of the generated function. If a function's body is inlined into the caller then the attribute no longer has a meaningful effect within the caller's body, and would be ignored.
78
-
* This does mean that any inline `asm!` calls in alternate instruction set functions could be inlined into the wrong instruction set within the caller's body. It would be up to the programmer to specify `inline(never)` if this is a concern. However, the primary goal of this RFC is to eliminate the need for inline `asm!`in the first place.
82
+
* This does mean that any inline `asm!` calls in alternate instruction set functions could be inlined into the wrong instruction set within the caller's body. That is one reason why `asm!`is unsafe.
79
83
80
84
How _specifically_ does it work on ARM:
81
-
* Within an ELF file, all `t32` code functions are stored as having odd value addresses, and when a branch-exchange (`bx`) or branch-link-exchange (`blx`) instruction is used then the target address's lowest bit is used to move the CPU between the `a32` and `t32` states appropriately.
82
-
* Accordingly, this does _not_ count as a full new ABI of its own. Both "Rust" and "C" ABI functions and function pointers are the same type as they were before.
83
-
* Linkers for ARM platforms such as [gnu ld](https://sourceware.org/binutils/docs/ld/ARM.html#ARM) have various flags to help the "interwork" process, depending on your compilation settings.
85
+
* Within an ELF file, all `t32` code functions are stored as having odd value addresses, and when a branch-exchange (`bx`) or branch-link-exchange (`blx`) instruction is used then the target address's lowest bit is used to move the CPU between the `a32` and `t32` states appropriately. See the [ARM ELF spec](https://static.docs.arm.com/ihi0044/g/aaelf32.pdf), section 5.5.3.
86
+
* Accordingly, this does _not_ count as a full new ABI of its own. Both "Rust" and "C" ABI functions and function pointers are the same type as they were before. See the [ARM Procedure Call Standard](https://developer.arm.com/docs/ihi0042/g/procedure-call-standard-for-the-arm-architecture-abi-2018q4-documentation).
87
+
* Linkers for ARM platforms such as [gnu ld](https://sourceware.org/binutils/docs/ld/ARM.html#ARM) have various flags to help the "interwork" process, depending on your compilation settings. In the case of GNU ld it's called [-mthumb-interwork](https://sourceware.org/binutils/docs/ld/ARM.html)
84
88
* This is considered a very low level and platform specific feature, so potentially having to pass additional linker args **is** considered an acceptable level of complexity for the programmer, though we should attempt to provide "good defaults" if we can of course.
85
89
90
+
TODO: `-mthumb-interwork` is an `as`/`gcc` arg, not an `ld` arg, fix the link above
91
+
86
92
# Drawbacks
87
93
[drawbacks]: #drawbacks
88
94
@@ -91,14 +97,82 @@ How _specifically_ does it work on ARM:
Here's a simple but complete-enough program of how this would be used in practice. In this example, the program is for the Game Boy Advance (GBA). I have attempted to limit it to the essentials, so all the MMIO definitions, as well as the assembly runtime you'd need to boot and call `main`, are still omitted from the example.
103
+
104
+
```rust
105
+
// The GBA's BIOS provides some functionality available via software
106
+
// interrupt. We expose them to Rust in our assumed assembly "runtime".
107
+
extern"C"fn {
108
+
/// Puts the CPU into a low-power state until a vblank interrupt,
109
+
/// and then returns after the interrupt handler completes.
110
+
VBlankInterWait(isize, isize);
111
+
}
112
+
113
+
// We assume that the MMIO stuff is imported from somewhere.
114
+
// The exact addresses and constant values aren't important.
115
+
modall_the_gba_mmio_definitions;
116
+
useall_the_gba_mmio_definitions::*;
117
+
118
+
fnmain() {
119
+
// All of the `write_volatile` calls here refer to
120
+
// the method of the `*mut T` type. Proper safe abstractions
121
+
// for all of this would complicate the example, so we
122
+
// simply use raw pointers and one large `unsafe` block.
1) We setup the device with our interrupt handler.
158
+
2) We set the device to have an interrupt every time the vertical blank starts.
159
+
3) We set the display to use a basic bitmap mode and begin our loop.
160
+
4) Each pass of the loop we wait for vetical blank, then draw a single pixel.
161
+
162
+
In the case of this particular device, the hardware interrupts go to the device's BIOS, which then calls your interrupt handler function. However, because the BIOS is `a32` code and uses a `b` branch instead of a `bx` branch-exchange, it jumps to the handler with the CPU in an `a32` state. If the handler were written as `t32` code it would immediately trigger UB.
163
+
164
+
## Alternatives
165
+
94
166
* Extending `target_feature` to allow `#[target_feature(disable = "...")]` and adding `thumb-mode` to the whitelist would support this functionality without adding another attribute; however, this is verbose, and does not fit with the `target_feature` attribute's current focus on features such as AVX and SSE whose absence is not necessarily compensated for by the presence of something else.
95
167
96
168
* Doing nothing is an option; it is currently possible to incorporate code using other instruction sets through means such as external assembly and build scripts. However, this has greatly reduced ergonomics.
97
169
170
+
* Of note is the fact that this is a feature that mostly improves Rust's support for the more legacy end of ARM devices. Newer devices, with much larger amounts of memory (relatively), don't usually benefit as much. They could simply compile the entire program as `a32`, without needing to gain the space savings of `t32` code.
171
+
98
172
# Prior art
99
173
[prior-art]: #prior-art
100
174
101
-
In C you can use `__attribute__((target("arm")))` and `__attribute__((target("thumb")))` to access similar functionality. It's a compiler-specific extension, but it's supported by both GCC and Clang.
175
+
In C you can use `__attribute__((target("arm")))` and `__attribute__((target("thumb")))` to access similar functionality. It's a compiler-specific extension, but it's supported by both GCC and Clang ([this PR](https://reviews.llvm.org/D33721) appears to be the one that added this feature to LLVM/clang).
102
176
103
177
# Unresolved questions
104
178
[unresolved-questions]: #unresolved-questions
@@ -113,3 +187,5 @@ In C you can use `__attribute__((target("arm")))` and `__attribute__((target("th
113
187
* If Rust gains support for the 65C816, the `#[instruction_set(?)]` attribute might be extended to allow shifting into its 65C02 compatibility mode and back again.
114
188
115
189
* MIPS has a 16-bit encoding which uses a similar scheme as ARM, where the low bit of a function's address is set when the 16-bit encoding is in use for that function.
190
+
191
+
* It might become possible to apply this attribute to trait prototypes in a future version. The main problems are properly specifying it and also that it would add additonal compiler complexity for very minimal gain (since each impl of the trait can use it on their impl of a method if they want).
0 commit comments