Skip to content

Commit 6c635ca

Browse files
committed
libtock2: Enable overriding the toolchain for the assembler
After some testing and upstream support, a variant of this will be upstreamed and this CL will be replaced by that. This uses the standard Make flags AS, ASFLAGS, and AR. STRIP is non-standard but was confirmed as fine to use by jrvanwhy. This also fixes a particularly weird issue with using llvm-mc currently: it cannot handle a comment on the same line as a `mv` instruction, unless it ends in `;`. Sounds like a tokenizer bug. BUG=b:210846576 TEST=make flash SOURCE=CHROME-ONLY Change-Id: I1a4c38cf918e43e4f3de45a0fb1851466f540c93
1 parent 6cddd58 commit 6c635ca

File tree

2 files changed

+136
-98
lines changed

2 files changed

+136
-98
lines changed

runtime/asm/asm_riscv32.S

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ start:
3333
* check is performed by comparing the program counter at the start to the
3434
* address of `start`, which is stored in rt_header. */
3535
auipc s0, 0 /* s0 = pc */
36-
mv a5, a0 /* Save rt_header so syscalls don't overwrite it */
36+
/* Note: the below semicolon is required due to an llvm-mc bug */
37+
mv a5, a0; /* Save rt_header so syscalls don't overwrite it */
3738
lw s1, 0(a5) /* s1 = rt_header.start */
3839
beq s0, s1, .Lset_brk /* Skip error handling code if pc is correct */
3940
/* If the beq on the previous line did not jump, then the binary is not at

runtime/extern_asm.rs

Lines changed: 134 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -2,118 +2,157 @@
22
//! point) and linking it into the process binary. Requires out_dir to be added
33
//! to rustc's link search path.
44
5-
/// Compiles the external assembly and tells cargo/rustc to link the resulting
6-
/// library into the crate. Panics if it is unable to find a working assembly
7-
/// toolchain or if the assembly fails to compile.
8-
pub(crate) fn build_and_link(out_dir: &str) {
9-
use std::env::var;
10-
let arch = var("CARGO_CFG_TARGET_ARCH").expect("Unable to read CARGO_CFG_TARGET_ARCH");
11-
12-
// Identify the toolchain configurations to try for the target architecture.
13-
// We support trying multiple toolchains because not all toolchains are
14-
// available on every OS that we want to support development on.
15-
let build_configs: &[AsmBuildConfig] = match arch.as_str() {
16-
"arm" => &[AsmBuildConfig {
17-
prefix: "arm-none-eabi",
18-
as_extra_args: &[],
19-
strip: false,
20-
}],
21-
"riscv32" => &[
22-
// First try riscv64-unknown-elf, as it is the toolchain used by
23-
// libtock-c and the toolchain used in the CI environment.
24-
AsmBuildConfig {
25-
prefix: "riscv64-unknown-elf",
26-
as_extra_args: &["-march=rv32imc"],
27-
strip: true,
28-
},
29-
// Second try riscv32-unknown-elf. This is the best match for Tock's
30-
// risc-v targets, but is not as widely available (and has not been
31-
// tested with libtock-rs yet).
32-
AsmBuildConfig {
33-
prefix: "riscv32-unknown-elf",
34-
as_extra_args: &[],
35-
strip: false, // Untested, may need to change.
36-
},
37-
// Last try riscv64-linux-gnu, as it is the only option on Debian 10
38-
AsmBuildConfig {
39-
prefix: "riscv64-linux-gnu",
40-
as_extra_args: &["-march=rv32imc"],
41-
strip: true,
42-
},
43-
],
44-
unknown_arch => {
45-
panic!("Unsupported architecture {}", unknown_arch);
46-
}
47-
};
5+
use std::{
6+
env, io,
7+
process::{self, Command},
8+
};
9+
10+
#[derive(Clone)]
11+
struct ToolchainInfo {
12+
arch: String,
13+
14+
/// The library archive command.
15+
ar_cmd: String,
16+
17+
/// The object file strip command, if used.
18+
/// If `None`, no strip will take place.
19+
strip_cmd: Option<String>,
20+
21+
/// The assembler command.
22+
as_cmd: String,
4823

49-
// Loop through toolchain configs until one works.
50-
for &build_config in build_configs {
51-
if try_build(&arch, build_config, out_dir).is_ok() {
52-
return;
24+
/// Additional flags to pass to the assembler.
25+
as_flags: Vec<String>,
26+
}
27+
28+
impl ToolchainInfo {
29+
fn from_gcc_prefix(arch: &str, prefix: &str, strip: bool) -> Self {
30+
Self {
31+
arch: arch.to_string(),
32+
ar_cmd: format!("{}-ar", prefix),
33+
strip_cmd: strip.then(|| format!("{}-strip", prefix)),
34+
as_cmd: format!("{}-as", prefix),
35+
as_flags: Vec::new(),
5336
}
5437
}
55-
56-
panic!("Unable to find a toolchain for architecture {}", arch);
5738
}
5839

59-
#[derive(Clone, Copy)]
60-
struct AsmBuildConfig {
61-
// Prefix, which is prepended to the command names.
62-
prefix: &'static str,
40+
/// Reads toolchain info from local environment variables, merging with a
41+
/// default toolchain if provided.
42+
///
43+
/// If a complete toolchain could not be constructed, this returns `None`.
44+
fn env_toolchain(arch: &str, default: Option<ToolchainInfo>) -> Option<ToolchainInfo> {
45+
let env_ar_cmd = env::var("AR").ok();
46+
let env_as_cmd = env::var("AS").ok();
47+
// TODO: this cannot handle whitespace in flags.
48+
let env_as_flags = env::var("ASFLAGS")
49+
.ok()
50+
.map(|x| x.split_ascii_whitespace().map(str::to_string).collect());
51+
let env_strip_cmd = env::var("STRIP").ok().filter(|x| !x.is_empty());
52+
53+
let ar_cmd;
54+
let as_cmd;
55+
let as_flags;
56+
let strip_cmd;
57+
if let Some(default) = default {
58+
// TODO: Should this merging occur or be all-or-nothing with
59+
// environment-provided toolchain variables?
60+
ar_cmd = env_ar_cmd.unwrap_or(default.ar_cmd);
61+
as_cmd = env_as_cmd.unwrap_or(default.as_cmd);
62+
as_flags = env_as_flags.unwrap_or(default.as_flags);
63+
strip_cmd = env_strip_cmd.or(default.strip_cmd);
64+
} else {
65+
ar_cmd = env_ar_cmd?;
66+
as_cmd = env_as_cmd?;
67+
as_flags = env_as_flags.unwrap_or(Vec::new());
68+
strip_cmd = env_strip_cmd;
69+
}
70+
71+
Some(ToolchainInfo {
72+
arch: arch.to_string(),
73+
ar_cmd,
74+
strip_cmd,
75+
as_cmd,
76+
as_flags,
77+
})
78+
}
6379

64-
// Extra arguments to pass to the assembler.
65-
as_extra_args: &'static [&'static str],
80+
/// Checks a toolchain, by running `--version` on the provided commands.
81+
fn test_toolchain(toolchain: &ToolchainInfo) -> io::Result<()> {
82+
Command::new(&toolchain.ar_cmd).arg("--version").status()?;
83+
Command::new(&toolchain.as_cmd).arg("--version").status()?;
84+
if let Some(ref path) = toolchain.strip_cmd {
85+
Command::new(path).arg("--version").status()?;
86+
}
87+
Ok(())
88+
}
6689

67-
// Do we need to strip the object file before packing it into the library
68-
// archive? This should be set to true on platforms where the assembler adds
69-
// local symbols to the object file.
70-
strip: bool,
90+
fn find_default_toolchain(arch: &str) -> Option<ToolchainInfo> {
91+
// The default toolchain is the first GCC-like toolchain that works as
92+
// expected. We support trying multiple toolchains because not all
93+
// toolchains are available on every OS that we want to support development.
94+
match arch {
95+
"arm" => {
96+
let toolchain = ToolchainInfo::from_gcc_prefix(arch, "arm-none-eabi", false);
97+
test_toolchain(&toolchain).ok().map(|_| toolchain)
98+
}
99+
"riscv32" => {
100+
for toolchain in [
101+
ToolchainInfo {
102+
as_flags: vec!["-march=rv32imc".to_string()],
103+
..ToolchainInfo::from_gcc_prefix(arch, "riscv64-unknown-elf", true)
104+
},
105+
// strip: false is untested here, may need to change
106+
ToolchainInfo::from_gcc_prefix(arch, "riscv32-unknown-elf", false),
107+
ToolchainInfo {
108+
as_flags: vec!["-march=rv32imc".to_string()],
109+
..ToolchainInfo::from_gcc_prefix(arch, "riscv64-linux-gnu", true)
110+
},
111+
] {
112+
if test_toolchain(&toolchain).is_ok() {
113+
return Some(toolchain);
114+
}
115+
}
116+
None
117+
}
118+
unknown_arch => {
119+
panic!("Unsupported architecture {}", unknown_arch)
120+
}
121+
}
71122
}
72123

73-
// Indicates the toolchain in the build config is unavailable.
74-
struct ToolchainUnavailable;
124+
/// Compiles the external assembly and tells cargo/rustc to link the resulting
125+
/// library into the crate. Panics if it is unable to find a working assembly
126+
/// toolchain or if the assembly fails to compile.
127+
pub(crate) fn build_and_link(out_dir: &str) {
128+
let arch = env::var("CARGO_CFG_TARGET_ARCH").expect("Unable to read CARGO_CFG_TARGET_ARCH");
129+
130+
match env_toolchain(&arch, find_default_toolchain(&arch)) {
131+
Some(toolchain) => build(&toolchain, out_dir),
132+
None => panic!("Unable to find a toolchain for architecture {}", arch),
133+
}
134+
}
75135

76-
fn try_build(
77-
arch: &str,
78-
build_config: AsmBuildConfig,
79-
out_dir: &str,
80-
) -> Result<(), ToolchainUnavailable> {
136+
fn build(toolchain: &ToolchainInfo, out_dir: &str) {
81137
use std::path::PathBuf;
82-
use std::process::Command;
83138

84139
// Invoke the assembler to produce an object file.
85-
let asm_source = &format!("asm/asm_{}.S", arch);
140+
let as_cmd = &toolchain.as_cmd;
141+
let asm_source = &format!("asm/asm_{}.S", toolchain.arch);
86142
let obj_file_path = [out_dir, "libtock_rt_asm.o"].iter().collect::<PathBuf>();
87143
let obj_file = obj_file_path.to_str().expect("Non-Unicode obj_file_path");
88-
let as_result = Command::new(format!("{}-as", build_config.prefix))
89-
.args(build_config.as_extra_args)
144+
let status = Command::new(as_cmd)
145+
.args(&toolchain.as_flags)
90146
.args(&[asm_source, "-o", obj_file])
91-
.status();
92-
93-
match as_result {
94-
Err(error) => {
95-
if error.kind() == std::io::ErrorKind::NotFound {
96-
// This `as` command does not exist. Return an error so
97-
// build_an_link can try another config (if one is available).
98-
return Err(ToolchainUnavailable);
99-
} else {
100-
panic!("Error invoking assembler: {}", error);
101-
}
102-
}
103-
Ok(status) => {
104-
assert!(status.success(), "Assembler returned an error");
105-
}
106-
}
107-
108-
// At this point, we know this toolchain is installed. We will fail if later
109-
// commands are uninstalled rather than trying a different build config.
147+
.status()
148+
.unwrap_or_else(|_| panic!("Failed to invoke {}", as_cmd));
149+
assert!(status.success(), "{} returned an error", as_cmd);
110150

111151
println!("cargo:rerun-if-changed={}", asm_source);
112152

113153
// Run `strip` if necessary.
114-
if build_config.strip {
115-
let strip_cmd = format!("{}-strip", build_config.prefix);
116-
let status = Command::new(&strip_cmd)
154+
if let Some(strip_cmd) = &toolchain.strip_cmd {
155+
let status = Command::new(strip_cmd)
117156
.args(&["-K", "start", "-K", "rust_start", obj_file])
118157
.status()
119158
.unwrap_or_else(|_| panic!("Failed to invoke {}", strip_cmd));
@@ -127,15 +166,15 @@ fn try_build(
127166
.iter()
128167
.collect();
129168
if let Err(error) = std::fs::remove_file(&archive_path) {
130-
if error.kind() != std::io::ErrorKind::NotFound {
169+
if error.kind() != io::ErrorKind::NotFound {
131170
panic!("Unable to remove archive file {}", archive_path.display());
132171
}
133172
}
134173

135174
// Create the library archive.
136-
let ar_cmd = format!("{}-ar", build_config.prefix);
175+
let ar_cmd = &toolchain.ar_cmd;
137176
let archive = archive_path.to_str().expect("Non-Unicode archive_path");
138-
let status = std::process::Command::new(&ar_cmd)
177+
let status = process::Command::new(ar_cmd)
139178
// c == Do not complain if archive needs to be created.
140179
// r == Insert or replace file in archive.
141180
.args(&["cr", archive, obj_file])
@@ -145,6 +184,4 @@ fn try_build(
145184

146185
// Tell rustc to link the binary against the library archive.
147186
println!("cargo:rustc-link-lib=static={}", ARCHIVE_NAME);
148-
149-
Ok(())
150187
}

0 commit comments

Comments
 (0)