Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
130 changes: 102 additions & 28 deletions crates/fbuild-build-mcu/src/ch32v/ch32v_linker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub struct Ch32vLinker {
profile: BuildProfile,
max_flash: Option<u64>,
max_ram: Option<u64>,
memory_defsyms: Vec<String>,
verbose: bool,
}

Expand Down Expand Up @@ -49,34 +50,29 @@ impl Ch32vLinker {
profile,
max_flash,
max_ram,
memory_defsyms: Vec::new(),
verbose,
}
}
}

#[async_trait::async_trait]
impl Linker for Ch32vLinker {
async fn archive(&self, objects: &[PathBuf], output: &Path) -> Result<()> {
crate::linker::LinkerBase::archive(&self.ar_path, objects, output, "riscv-none-elf-ar")
.await
pub fn with_memory_defsyms(mut self, flags: Vec<String>) -> Self {
self.memory_defsyms = flags;
self
}

async fn link(
fn build_link_args(
&self,
objects: &[PathBuf],
archives: &[PathBuf],
output_dir: &Path,
elf_path: &Path,
map_path: &Path,
extra: &LinkExtraArgs,
) -> Result<PathBuf> {
std::fs::create_dir_all(output_dir)?;
let elf_path = output_dir.join("firmware.elf");

) -> Vec<String> {
let mut args: Vec<String> = vec![self.gcc_path.to_string_lossy().to_string()];

// Linker flags from config
args.extend(self.mcu_config.linker_flags.iter().cloned());
args.extend(self.memory_defsyms.iter().cloned());

// Profile-specific link flags
if let Some(profile) = self.mcu_config.get_profile(self.profile.as_dir_name()) {
args.extend(profile.link_flags.iter().cloned());
}
Expand All @@ -87,24 +83,43 @@ impl Linker for Ch32vLinker {
"-o".to_string(),
elf_path.to_string_lossy().to_string(),
]);

// Always emit a linker map next to firmware.elf for debugging (#305).
let map_path = output_dir.join("firmware.map");
args.push(format!("-Wl,-Map={}", map_path.to_string_lossy()));

// Sketch objects first
for obj in objects {
args.push(obj.to_string_lossy().to_string());
}

// Core objects passed directly (not archived) for LTO compatibility
for archive in archives {
args.push(archive.to_string_lossy().to_string());
}

// Linker libraries from config
args.extend(
objects
.iter()
.map(|path| path.to_string_lossy().to_string()),
);
args.extend(
archives
.iter()
.map(|path| path.to_string_lossy().to_string()),
);
args.extend(self.mcu_config.linker_libs.iter().cloned());
args.extend(extra.libs.iter().cloned());
args
}
}

#[async_trait::async_trait]
impl Linker for Ch32vLinker {
async fn archive(&self, objects: &[PathBuf], output: &Path) -> Result<()> {
crate::linker::LinkerBase::archive(&self.ar_path, objects, output, "riscv-none-elf-ar")
.await
}

async fn link(
&self,
objects: &[PathBuf],
archives: &[PathBuf],
output_dir: &Path,
extra: &LinkExtraArgs,
) -> Result<PathBuf> {
std::fs::create_dir_all(output_dir)?;
let elf_path = output_dir.join("firmware.elf");

let map_path = output_dir.join("firmware.map");
let args = self.build_link_args(objects, archives, &elf_path, &map_path, extra);

if self.verbose {
tracing::debug!(target: "fbuild_build::linker::ch32v", "link: {}", args.join(" "));
Expand Down Expand Up @@ -214,4 +229,63 @@ mod tests {
.contains("link.ld")
);
}

#[test]
fn test_build_link_args_includes_memory_defsyms_before_script() {
let linker = Ch32vLinker::new(
PathBuf::from("gcc"),
PathBuf::from("ar"),
PathBuf::from("objcopy"),
PathBuf::from("size"),
PathBuf::from("link.ld"),
get_ch32v_config_for_mcu("ch32v003").unwrap(),
BuildProfile::Release,
None,
None,
false,
)
.with_memory_defsyms(vec![
"-Wl,--defsym=__FLASH_SIZE=32768".into(),
"-Wl,--defsym=__RAM_SIZE=10240".into(),
]);
let args = linker.build_link_args(
&[],
&[],
Path::new("firmware.elf"),
Path::new("firmware.map"),
&LinkExtraArgs::default(),
);
let script_index = args.iter().position(|arg| arg.starts_with("-T")).unwrap();
for flag in [
"-Wl,--defsym=__FLASH_SIZE=32768",
"-Wl,--defsym=__RAM_SIZE=10240",
] {
let index = args.iter().position(|arg| arg == flag).unwrap();
assert!(index < script_index);
}
}

#[test]
fn test_build_link_args_without_memory_defsyms() {
let linker = Ch32vLinker::new(
PathBuf::from("gcc"),
PathBuf::from("ar"),
PathBuf::from("objcopy"),
PathBuf::from("size"),
PathBuf::from("link.ld"),
get_ch32v_config_for_mcu("ch32v003").unwrap(),
BuildProfile::Release,
None,
None,
false,
);
let args = linker.build_link_args(
&[],
&[],
Path::new("firmware.elf"),
Path::new("firmware.map"),
&LinkExtraArgs::default(),
);
assert!(!args.iter().any(|arg| arg.contains("--defsym")));
}
}
8 changes: 7 additions & 1 deletion crates/fbuild-build-mcu/src/ch32v/orchestrator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,11 @@ impl BuildOrchestrator for Ch32vOrchestrator {
.join("SRC")
.join("Ld")
.join("Link.ld");
let mut memory_defsyms = Vec::new();
if let (Some(flash), Some(ram)) = (ctx.board.max_flash, ctx.board.max_ram) {
memory_defsyms.push(format!("-Wl,--defsym=__FLASH_SIZE={flash}"));
memory_defsyms.push(format!("-Wl,--defsym=__RAM_SIZE={ram}"));
}
let linker = Ch32vLinker::new(
toolchain.get_gcc_path(),
toolchain.get_ar_path(),
Expand All @@ -187,7 +192,8 @@ impl BuildOrchestrator for Ch32vOrchestrator {
ctx.board.max_flash,
ctx.board.max_ram,
params.verbose,
);
)
.with_memory_defsyms(memory_defsyms);

// 8. Build LibraryBuildEnv for project-as-library compilation
let gcc_path = toolchain.get_gcc_path();
Expand Down
Loading