From 6cc9406ad5f240862c9237fd4aeef78393be2fda Mon Sep 17 00:00:00 2001 From: zackees Date: Tue, 21 Jul 2026 16:35:20 -0700 Subject: [PATCH] fix(ch32v): pass board memory sizes to linker Closes #1103 --- .../src/ch32v/ch32v_linker.rs | 130 ++++++++++++++---- .../src/ch32v/orchestrator.rs | 8 +- 2 files changed, 109 insertions(+), 29 deletions(-) diff --git a/crates/fbuild-build-mcu/src/ch32v/ch32v_linker.rs b/crates/fbuild-build-mcu/src/ch32v/ch32v_linker.rs index ec24bbf08..f193cdf94 100644 --- a/crates/fbuild-build-mcu/src/ch32v/ch32v_linker.rs +++ b/crates/fbuild-build-mcu/src/ch32v/ch32v_linker.rs @@ -22,6 +22,7 @@ pub struct Ch32vLinker { profile: BuildProfile, max_flash: Option, max_ram: Option, + memory_defsyms: Vec, verbose: bool, } @@ -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) -> 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 { - std::fs::create_dir_all(output_dir)?; - let elf_path = output_dir.join("firmware.elf"); - + ) -> Vec { let mut args: Vec = 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()); } @@ -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 { + 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(" ")); @@ -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"))); + } } diff --git a/crates/fbuild-build-mcu/src/ch32v/orchestrator.rs b/crates/fbuild-build-mcu/src/ch32v/orchestrator.rs index 2723cfcc6..f21e581a7 100644 --- a/crates/fbuild-build-mcu/src/ch32v/orchestrator.rs +++ b/crates/fbuild-build-mcu/src/ch32v/orchestrator.rs @@ -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(), @@ -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();