Creating a custom target
If a custom target triple is not available for your platform, you must create a custom target file that describes your target to rustc.
Keep in mind that it is required to use a nightly compiler to build the core library, which must be done for a target unknown to rustc.
Deciding on a target triple
Many targets already have a known triple used to describe them, typically in the form ARCH-VENDOR-SYS-ABI. You should aim to use the same triple that LLVM uses; however, it may differ if you need to specify additional information to Rust that LLVM does not know about. Although the triple is technically only for human use, it's important for it to be unique and descriptive especially if the target will be upstreamed in the future.
The ARCH part is typically just the architecture name, except in the case of 32-bit ARM. For
example, you would probably use x86_64 for those processors, but specify the exact ARM architecture
version. Typical values might be armv7
, armv5te
, or thumbv7neon
. Take a look at the names of
the built-in targets for inspiration.
The VENDOR part is optional and describes the manufacturer. Omitting this field is the same as
using unknown
.
The SYS part describes the OS that is used. Typical values include win32
, linux
, and darwin
for desktop platforms. none
is used for bare-metal usage.
The ABI part describes how the process starts up. eabi
is used for bare metal, while gnu
is used
for glibc, musl
for musl, etc.
Now that you have a target triple, create a file with the name of the triple and a .json
extension. For example, a file describing armv7a-none-eabi
would have the filename
armv7a-none-eabi.json
.
Fill the target file
The target file must be valid JSON. There are two places where its contents are described:
Target
, where every field is mandatory, and TargetOptions
, where every field is optional.
All underscores are replaced with hyphens.
The recommended way is to base your target file on the specification of a built-in target that's
similar to your target system, then tweak it to match the properties of your target system. To do
so, use the command
rustc +nightly -Z unstable-options --print target-spec-json --target $SOME_SIMILAR_TARGET
, using
a target that's already built into the compiler.
You can pretty much copy that output into your file. Start with a few modifications:
- Remove
"is-builtin": true
- Fill
llvm-target
with the triple that LLVM expects - Decide on a panicking strategy. A bare metal implementation will likely use
"panic-strategy": "abort"
. If you decide not toabort
on panicking, unless you tell Cargo to per-project, you must define an eh_personality function. - Configure atomics. Pick the first option that describes your target:
- I have a single-core processor, no threads, no interrupts, or any way for
multiple things to be happening in parallel: if you are sure that is the case, such as WASM
(for now), you may set
"singlethread": true
. This will configure LLVM to convert all atomic operations to use their single threaded counterparts. Incorrectly using this option may result in UB if using threads or interrupts. - I have native atomic operations: set
max-atomic-width
to the biggest type in bits that your target can operate on atomically. For example, many ARM cores have 32-bit atomic operations. You may set"max-atomic-width": 32
in that case. - I have no native atomic operations, but I can emulate them myself: set
max-atomic-width
to the highest number of bits that you can emulate up to 128, then implement all of the atomic and sync functions expected by LLVM as#[no_mangle] unsafe extern "C"
. These functions have been standardized by gcc, so the gcc documentation may have more notes. Missing functions will cause a linker error, while incorrectly implemented functions will possibly cause UB. For example, if you have a single-core, single-thread processor with interrupts, you can implement these functions to disable interrupts, perform the regular operation, and then re-enable them. - I have no native atomic operations: you'll have to do some unsafe work to manually ensure
synchronization in your code. You must set
"max-atomic-width": 0
.
- I have a single-core processor, no threads, no interrupts, or any way for
multiple things to be happening in parallel: if you are sure that is the case, such as WASM
(for now), you may set
- Change the linker if integrating with an existing toolchain. For example, if you're using a
toolchain that uses a custom build of gcc, set
"linker-flavor": "gcc"
andlinker
to the command name of your linker. If you require additional linker arguments, usepre-link-args
andpost-link-args
as so:
Ensure that the linker type is the key within"pre-link-args": { "gcc": [ "-Wl,--as-needed", "-Wl,-z,noexecstack", "-m64" ] }, "post-link-args": { "gcc": [ "-Wl,--allow-multiple-definition", "-Wl,--start-group,-lc,-lm,-lgcc,-lstdc++,-lsupc++,--end-group" ] }
link-args
. - Configure LLVM features. Run
llc -march=ARCH -mattr=help
where ARCH is the base architecture (not including the version in the case of ARM) to list the available features and their descriptions. If your target requires strict memory alignment access (e.g.armv5te
), make sure that you enablestrict-align
. To enable a feature, place a plus before it. Likewise, to disable a feature, place a minus before it. Features should be comma separated like so:"features": "+soft-float,+neon
. Note that this may not be necessary if LLVM knows enough about your target based on the provided triple and CPU. - Configure the CPU that LLVM uses if you know it. This will enable CPU-specific optimizations and
features. At the top of the output of the command in the last step, there is a list of known CPUs.
If you know that you will be targeting a specific CPU, you may set it in the
cpu
field in the JSON target file.
Use the target file
Once you have a target specification file, you may refer to it by its path or by its name (i.e.
excluding .json
) if it is in the current directory or in $RUST_TARGET_PATH
.
Verify that it is readable by rustc:
❱ rustc --print cfg --target foo.json # or just foo if in the current directory
debug_assertions
target_arch="arm"
target_endian="little"
target_env=""
target_feature="mclass"
target_feature="v7"
target_has_atomic="16"
target_has_atomic="32"
target_has_atomic="8"
target_has_atomic="cas"
target_has_atomic="ptr"
target_os="none"
target_pointer_width="32"
target_vendor=""
Now, you finally get to use it! Many resources have been recommending xargo
or cargo-xbuild
.
However, its successor, cargo's build-std
feature, has received a lot of work recently and has
quickly reached feature parity with the other options. As such, this guide will only cover that
option.
Start with a bare minimum no_std
program. Now, run
cargo build -Z build-std=core --target foo.json
, again using the above rules about referencing the
path. Hopefully, you should now have a binary in the target directory.
You may optionally configure cargo to always use your target. See the recommendations at the end of
the page about the smallest no_std
program. However, you'll currently have to
use the flag -Z build-std=core
as that option is unstable.
Build additional built-in crates
When using cargo's build-std
feature, you can choose which crates to compile in. By default, when
only passing -Z build-std
, std
, core
, and alloc
are compiled. However, you may want to
exclude std
when compiling for bare-metal. To do so, specify the crated you'd like after
build-std
. For example, to include core
and alloc
, pass -Z build-std=core,alloc
.
Troubleshooting
language item required, but not found: eh_personality
Either add "panic-strategy": "abort"
to your target file, or define an eh_personality function.
Alternatively, tell Cargo to ignore it.
undefined reference to __sync_val_compare_and_swap_#
Rust thinks that your target has atomic instructions, but LLVM doesn't. Go back to the step about
configuring atomics. You will need to reduce the number in max-atomic-width
.
See #58500 for more details.
could not find sync
in alloc
Similar to the above case, Rust doesn't think that you have atomics. You must implement them yourself or tell Rust that you have atomic instructions.
multiple definition of __(something)
You're likely linking your Rust program with code built from another language, and the other language includes compiler built-ins that Rust also creates. To fix this, you'll need to tell your linker to allow multiple definitions. If using gcc, you may add:
"post-link-args": {
"gcc": [
"-Wl,--allow-multiple-definition"
]
}
error adding symbols: file format not recognized
Switch to cargo's build-std
feature and update your compiler. This was a bug introduced
for a few compiler builds that tried to pass in internal Rust object to an external linker.