feat: support //export with different symbol names for embedded targets

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>
This commit is contained in:
xgopilot
2025-10-31 03:31:39 +00:00
parent 86cafff113
commit 060a2dea06
3 changed files with 87 additions and 5 deletions

View File

@@ -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))
}

View File

@@ -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! ✓"

View File

@@ -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