From 060a2dea06cefbe8b0d127ba5394cd378c355e8f Mon Sep 17 00:00:00 2001 From: xgopilot Date: Fri, 31 Oct 2025 03:31:39 +0000 Subject: [PATCH] feat: support //export with different symbol names for embedded targets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements support for TinyGo-style //export directive that allows exporting functions with symbol names different from the Go function name. This is essential for embedded development where hardware specifications require specific symbol names (e.g., ARM Cortex-M interrupt handlers like LPSPI2_IRQHandler, SysTick_Handler). Changes: - Modified cl/import.go to support two export formats: 1. //export ExportName (TinyGo-style, export name can differ from function name) 2. //export FuncName ExportName (Go-style, explicit function and export names) - Added test case in _demo/embedded/export_test/ with nm verification Example usage: //export LPSPI2_IRQHandler func interruptLPSPI2() { // exported as LPSPI2_IRQHandler } Fixes #1378 🤖 Generated with [codeagent](https://github.com/qbox/codeagent) Co-authored-by: luoliwoshang <51194195+luoliwoshang@users.noreply.github.com> --- _demo/embedded/export_test/main.go | 22 +++++++++++++++ _demo/embedded/export_test/test.sh | 43 ++++++++++++++++++++++++++++++ cl/import.go | 27 +++++++++++++++---- 3 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 _demo/embedded/export_test/main.go create mode 100755 _demo/embedded/export_test/test.sh diff --git a/_demo/embedded/export_test/main.go b/_demo/embedded/export_test/main.go new file mode 100644 index 00000000..ec29c390 --- /dev/null +++ b/_demo/embedded/export_test/main.go @@ -0,0 +1,22 @@ +package main + +//export LPSPI2_IRQHandler +func interruptLPSPI2() { + println("LPSPI2 interrupt handled") +} + +//export SysTick_Handler +func systemTickHandler() { + println("System tick") +} + +//export Add +func Add(a, b int) int { + return a + b +} + +func main() { + interruptLPSPI2() + systemTickHandler() + println("Add(2, 3) =", Add(2, 3)) +} diff --git a/_demo/embedded/export_test/test.sh b/_demo/embedded/export_test/test.sh new file mode 100755 index 00000000..402850bb --- /dev/null +++ b/_demo/embedded/export_test/test.sh @@ -0,0 +1,43 @@ +#!/bin/bash +set -e + +echo "Testing //export with different symbol names..." + +# Set LLGO_ROOT to repository root +export LLGO_ROOT=/workspace + +# Build the test program +echo "Building test program..." +/workspace/llgo build -o test_export main.go || exit 1 + +# Check for expected symbols using nm +echo "Verifying symbols with nm..." + +# Should find LPSPI2_IRQHandler (not interruptLPSPI2) +if nm test_export | grep -q "T LPSPI2_IRQHandler"; then + echo "✓ Symbol LPSPI2_IRQHandler found" +else + echo "✗ Symbol LPSPI2_IRQHandler not found" + echo "Available symbols:" + nm test_export | grep " T " + exit 1 +fi + +# Should find SysTick_Handler (not systemTickHandler) +if nm test_export | grep -q "T SysTick_Handler"; then + echo "✓ Symbol SysTick_Handler found" +else + echo "✗ Symbol SysTick_Handler not found" + exit 1 +fi + +# Should find Add (same name) +if nm test_export | grep -q "T Add"; then + echo "✓ Symbol Add found" +else + echo "✗ Symbol Add not found" + exit 1 +fi + +echo "" +echo "All symbol checks passed! ✓" diff --git a/cl/import.go b/cl/import.go index ea472819..9cae2f68 100644 --- a/cl/import.go +++ b/cl/import.go @@ -278,7 +278,8 @@ func (p *context) initLinknameByDoc(doc *ast.CommentGroup, fullName, inPkgName s for n := len(doc.List) - 1; n >= 0; n-- { line := doc.List[n].Text ret := p.initLinkname(line, func(name string) (_ string, _, ok bool) { - return fullName, isVar, name == inPkgName + // support empty name for //export directive + return fullName, isVar, name == inPkgName || name == "" }) if ret != unknownDirective { return ret == hasLinkname @@ -312,10 +313,26 @@ func (p *context) initLinkname(line string, f func(inPkgName string) (fullName s p.initLink(line, len(llgolink), false, f) return hasLinkname } else if strings.HasPrefix(line, export) { - // rewrite //export FuncName to //export FuncName FuncName - funcName := strings.TrimSpace(line[len(export):]) - line = line + " " + funcName - p.initLink(line, len(export), true, f) + // support //export ExportName + // format: //export ExportName or //export FuncName ExportName + exportName := strings.TrimSpace(line[len(export):]) + var inPkgName string + if idx := strings.IndexByte(exportName, ' '); idx > 0 { + // format: //export FuncName ExportName (go-style) + inPkgName = exportName[:idx] + exportName = strings.TrimLeft(exportName[idx+1:], " ") + } else { + // format: //export ExportName (tinygo-style) + // use empty string to match any function + inPkgName = "" + } + if fullName, _, ok := f(inPkgName); ok { + p.prog.SetLinkname(fullName, exportName) + p.pkg.SetExport(fullName, exportName) + } else { + fmt.Fprintln(os.Stderr, "==>", line) + fmt.Fprintf(os.Stderr, "llgo: export %s not found and ignored\n", inPkgName) + } return hasLinkname } else if strings.HasPrefix(line, directive) { // skip unknown annotation but continue to parse the next annotation