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:
@@ -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
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
19
cl/import.go
19
cl/import.go
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user