Merge pull request #1384 from luoliwoshang/feature/export-different-names-1378

cl: support //export with different symbol names on embedded targets
This commit is contained in:
xushiwei
2025-11-17 08:10:09 +08:00
committed by GitHub
7 changed files with 395 additions and 9 deletions

View File

@@ -393,10 +393,10 @@ func TestErrImport(t *testing.T) {
func TestErrInitLinkname(t *testing.T) {
var ctx context
ctx.initLinkname("//llgo:link abc", func(name string) (string, bool, bool) {
ctx.initLinkname("//llgo:link abc", func(name string, isExport bool) (string, bool, bool) {
return "", false, false
})
ctx.initLinkname("//go:linkname Printf printf", func(name string) (string, bool, bool) {
ctx.initLinkname("//go:linkname Printf printf", func(name string, isExport bool) (string, bool, bool) {
return "", false, false
})
defer func() {
@@ -404,7 +404,7 @@ func TestErrInitLinkname(t *testing.T) {
t.Fatal("initLinkname: no error?")
}
}()
ctx.initLinkname("//go:linkname Printf printf", func(name string) (string, bool, bool) {
ctx.initLinkname("//go:linkname Printf printf", func(name string, isExport bool) (string, bool, bool) {
return "foo.Printf", false, name == "Printf"
})
}
@@ -506,3 +506,238 @@ func TestInstantiate(t *testing.T) {
t.Fatal("error")
}
}
func TestHandleExportDiffName(t *testing.T) {
tests := []struct {
name string
enableExportRename bool
line string
fullName string
inPkgName string
wantHasLinkname bool
wantLinkname string
wantExport string
}{
{
name: "ExportDiffNames_DifferentName",
enableExportRename: true,
line: "//export IRQ_Handler",
fullName: "pkg.HandleInterrupt",
inPkgName: "HandleInterrupt",
wantHasLinkname: true,
wantLinkname: "IRQ_Handler",
wantExport: "IRQ_Handler",
},
{
name: "ExportDiffNames_SameName",
enableExportRename: true,
line: "//export SameName",
fullName: "pkg.SameName",
inPkgName: "SameName",
wantHasLinkname: true,
wantLinkname: "SameName",
wantExport: "SameName",
},
{
name: "ExportDiffNames_WithSpaces",
enableExportRename: true,
line: "//export Timer_Callback ",
fullName: "pkg.OnTimerTick",
inPkgName: "OnTimerTick",
wantHasLinkname: true,
wantLinkname: "Timer_Callback",
wantExport: "Timer_Callback",
},
{
name: "ExportDiffNames_Disabled_MatchingName",
enableExportRename: false,
line: "//export Func",
fullName: "pkg.Func",
inPkgName: "Func",
wantHasLinkname: true,
wantLinkname: "Func",
wantExport: "Func",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Save and restore global state
oldEnableExportRename := enableExportRename
defer func() {
EnableExportRename(oldEnableExportRename)
}()
EnableExportRename(tt.enableExportRename)
// Setup context
prog := llssa.NewProgram(nil)
pkg := prog.NewPackage("test", "test")
ctx := &context{
prog: prog,
pkg: pkg,
}
// Call initLinkname with closure that mimics initLinknameByDoc behavior
ret := ctx.initLinkname(tt.line, func(name string, isExport bool) (string, bool, bool) {
return tt.fullName, false, name == tt.inPkgName || (isExport && enableExportRename)
})
// Verify result
hasLinkname := (ret == hasLinkname)
if hasLinkname != tt.wantHasLinkname {
t.Errorf("hasLinkname = %v, want %v", hasLinkname, tt.wantHasLinkname)
}
if tt.wantHasLinkname {
// Check linkname was set
if link, ok := prog.Linkname(tt.fullName); !ok || link != tt.wantLinkname {
t.Errorf("linkname = %q (ok=%v), want %q", link, ok, tt.wantLinkname)
}
// Check export was set
exports := pkg.ExportFuncs()
if export, ok := exports[tt.fullName]; !ok || export != tt.wantExport {
t.Errorf("export = %q (ok=%v), want %q", export, ok, tt.wantExport)
}
}
})
}
}
func TestInitLinknameByDocExportDiffNames(t *testing.T) {
tests := []struct {
name string
enableExportRename bool
doc *ast.CommentGroup
fullName string
inPkgName string
wantExported bool // Whether the symbol should be exported with different name
wantLinkname string
wantExport string
}{
{
name: "WithExportDiffNames_DifferentNameExported",
enableExportRename: true,
doc: &ast.CommentGroup{
List: []*ast.Comment{
{Text: "//export IRQ_Handler"},
},
},
fullName: "pkg.HandleInterrupt",
inPkgName: "HandleInterrupt",
wantExported: true,
wantLinkname: "IRQ_Handler",
wantExport: "IRQ_Handler",
},
{
name: "WithoutExportDiffNames_NotExported",
enableExportRename: false,
doc: &ast.CommentGroup{
List: []*ast.Comment{
{Text: "//export DifferentName"},
},
},
fullName: "pkg.HandleInterrupt",
inPkgName: "HandleInterrupt",
wantExported: false,
// Without enableExportRename, it goes through normal flow which expects same name
// The symbol "DifferentName" won't be found, so no export happens
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Without enableExportRename, export with different name will panic
if !tt.wantExported && !tt.enableExportRename {
defer func() {
if r := recover(); r == nil {
t.Error("expected panic for export with different name when enableExportRename=false")
}
}()
}
// Save and restore global state
oldEnableExportRename := enableExportRename
defer func() {
EnableExportRename(oldEnableExportRename)
}()
EnableExportRename(tt.enableExportRename)
// Setup context
prog := llssa.NewProgram(nil)
pkg := prog.NewPackage("test", "test")
ctx := &context{
prog: prog,
pkg: pkg,
}
// Call initLinknameByDoc
ctx.initLinknameByDoc(tt.doc, tt.fullName, tt.inPkgName, false)
// Verify export behavior
exports := pkg.ExportFuncs()
if tt.wantExported {
// Should have exported the symbol with different name
if export, ok := exports[tt.fullName]; !ok || export != tt.wantExport {
t.Errorf("export = %q (ok=%v), want %q", export, ok, tt.wantExport)
}
// Check linkname was also set
if link, ok := prog.Linkname(tt.fullName); !ok || link != tt.wantLinkname {
t.Errorf("linkname = %q (ok=%v), want %q", link, ok, tt.wantLinkname)
}
}
})
}
}
func TestInitLinkExportDiffNames(t *testing.T) {
tests := []struct {
name string
enableExportRename bool
line string
wantPanic bool
}{
{
name: "ExportDiffNames_Enabled_NoError",
enableExportRename: true,
line: "//export IRQ_Handler",
wantPanic: false,
},
{
name: "ExportDiffNames_Disabled_Panic",
enableExportRename: false,
line: "//export IRQ_Handler",
wantPanic: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.wantPanic {
defer func() {
if r := recover(); r == nil {
t.Error("expected panic but didn't panic")
}
}()
}
oldEnableExportRename := enableExportRename
defer func() {
EnableExportRename(oldEnableExportRename)
}()
EnableExportRename(tt.enableExportRename)
prog := llssa.NewProgram(nil)
pkg := prog.NewPackage("test", "test")
ctx := &context{
prog: prog,
pkg: pkg,
}
ctx.initLinkname(tt.line, func(inPkgName string, isExport bool) (fullName string, isVar, ok bool) {
// Simulate initLinknames scenario: symbol not found (like in decl packages)
return "", false, false
})
})
}
}

View File

@@ -53,6 +53,11 @@ var (
enableDbg bool
enableDbgSyms bool
disableInline bool
// enableExportRename enables //export to use different C symbol names than Go function names.
// This is for TinyGo compatibility when using -target flag for embedded targets.
// Currently, using -target implies TinyGo embedded target mode.
enableExportRename bool
)
// SetDebug sets debug flags.
@@ -73,6 +78,12 @@ func EnableTrace(b bool) {
enableCallTracing = b
}
// EnableExportRename enables or disables //export with different C symbol names.
// This is enabled when using -target flag for TinyGo compatibility.
func EnableExportRename(b bool) {
enableExportRename = b
}
// -----------------------------------------------------------------------------
type instrOrValue interface {

View File

@@ -73,7 +73,7 @@ func (p *pkgSymInfo) initLinknames(ctx *context) {
lines := bytes.Split(b, sep)
for _, line := range lines {
if bytes.HasPrefix(line, commentPrefix) {
ctx.initLinkname(string(line), func(inPkgName string) (fullName string, isVar, ok bool) {
ctx.initLinkname(string(line), func(inPkgName string, isExport bool) (fullName string, isVar, ok bool) {
if sym, ok := p.syms[inPkgName]; ok && file == sym.file {
return sym.fullName, sym.isVar, true
}
@@ -277,8 +277,8 @@ func (p *context) initLinknameByDoc(doc *ast.CommentGroup, fullName, inPkgName s
if doc != nil {
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
ret := p.initLinkname(line, func(name string, isExport bool) (_ string, _, ok bool) {
return fullName, isVar, name == inPkgName || (isExport && enableExportRename)
})
if ret != unknownDirective {
return ret == hasLinkname
@@ -294,7 +294,7 @@ const (
unknownDirective = -1
)
func (p *context) initLinkname(line string, f func(inPkgName string) (fullName string, isVar, ok bool)) int {
func (p *context) initLinkname(line string, f func(inPkgName string, isExport bool) (fullName string, isVar, ok bool)) int {
const (
linkname = "//go:linkname "
llgolink = "//llgo:link "
@@ -324,17 +324,24 @@ func (p *context) initLinkname(line string, f func(inPkgName string) (fullName s
return noDirective
}
func (p *context) initLink(line string, prefix int, export bool, f func(inPkgName string) (fullName string, isVar, ok bool)) {
func (p *context) initLink(line string, prefix int, export bool, f func(inPkgName string, isExport bool) (fullName string, isVar, ok bool)) {
text := strings.TrimSpace(line[prefix:])
if idx := strings.IndexByte(text, ' '); idx > 0 {
inPkgName := text[:idx]
if fullName, _, ok := f(inPkgName); ok {
if fullName, _, ok := f(inPkgName, export); ok {
link := strings.TrimLeft(text[idx+1:], " ")
p.prog.SetLinkname(fullName, link)
if export {
p.pkg.SetExport(fullName, link)
}
} else {
// Export with different names already processed by initLinknameByDoc
if export && enableExportRename {
return
}
if export {
panic(fmt.Sprintf("export comment has wrong name %q", inPkgName))
}
fmt.Fprintln(os.Stderr, "==>", line)
fmt.Fprintf(os.Stderr, "llgo: linkname %s not found and ignored\n", inPkgName)
}