From 82f991cb788d92a24faa5ed86435852402dbf46b Mon Sep 17 00:00:00 2001 From: Garrett Bodley Date: Tue, 20 Aug 2024 17:33:22 -0400 Subject: [PATCH] build,ir,printer: add InternalFunction Adds support for internal assembly functions. The new InternalFunction() call outputs a TEXT directive sans the pesky unicode dot used in Go assembly. This allows Avo to generate an internal assembly function that is not linked to any symbols in the corresponding package. Closes #442 --- build/context.go | 11 +++++++++++ build/global.go | 3 +++ ir/ir.go | 11 +++++++++++ ir/ir_test.go | 14 ++++++++++++++ printer/goasm.go | 13 +++++++++++-- printer/goasm_test.go | 39 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 89 insertions(+), 2 deletions(-) diff --git a/build/context.go b/build/context.go index 80431305..8f784a02 100644 --- a/build/context.go +++ b/build/context.go @@ -90,6 +90,12 @@ func (c *Context) Function(name string) { c.file.AddSection(c.function) } +// InternalFunction starts building a new internal function (not linked to any package) with the given name. +func (c *Context) InternalFunction(name string) { + c.function = ir.NewInternalFunction(name) + c.file.AddSection(c.function) +} + // Doc sets documentation comment lines for the currently active function. func (c *Context) Doc(lines ...string) { c.activefunc().Doc = lines @@ -112,6 +118,11 @@ func (c *Context) Signature(s *gotypes.Signature) { // SignatureExpr parses the signature expression and sets it as the active function's signature. func (c *Context) SignatureExpr(expr string) { + if c.activefunc().IsInternal { + e := fmt.Sprintf("cannot link SignatureExpr \"%s\" with InternalFunction \"%s\"", expr, c.activefunc().Name) + c.adderror(errors.New(e)) + return + } s, err := gotypes.ParseSignatureInPackage(c.types(), expr) if err != nil { c.adderror(err) diff --git a/build/global.go b/build/global.go index dae6795b..11750efc 100644 --- a/build/global.go +++ b/build/global.go @@ -127,6 +127,9 @@ func Dereference(ptr gotypes.Component) gotypes.Component { return ctx.Dereferen // Function starts building a new function with the given name. func Function(name string) { ctx.Function(name) } +// InternalFunction starts building a new internal function (not linked to any package) with the given name. +func InternalFunction(name string) { ctx.InternalFunction(name) } + // Doc sets documentation comment lines for the currently active function. func Doc(lines ...string) { ctx.Doc(lines...) } diff --git a/ir/ir.go b/ir/ir.go index 871d4881..d693942d 100644 --- a/ir/ir.go +++ b/ir/ir.go @@ -176,6 +176,7 @@ type Function struct { Doc []string Signature *gotypes.Signature LocalSize int + IsInternal bool Nodes []Node @@ -195,10 +196,20 @@ func (f *Function) section() {} func NewFunction(name string) *Function { return &Function{ Name: name, + IsInternal: false, Signature: gotypes.NewSignatureVoid(), } } +// NewInternalFunction builds an empty internal function (not linked to any package) of the given name. +func NewInternalFunction(name string) *Function { + return &Function{ + Name: name, + IsInternal: true, + Signature: gotypes.NewSignatureVoid(), + } +} + // AddPragma adds a pragma to this function. func (f *Function) AddPragma(directive string, args ...string) { f.Pragmas = append(f.Pragmas, Pragma{ diff --git a/ir/ir_test.go b/ir/ir_test.go index f37e5ecf..a2acc503 100644 --- a/ir/ir_test.go +++ b/ir/ir_test.go @@ -24,6 +24,20 @@ func TestFunctionLabels(t *testing.T) { } } +func TestIsNotInternal(t *testing.T) { + f := NewFunction("isNotInternal") + if f.IsInternal { + t.Fatalf("expected f.IsInternal to be false, got %t", f.IsInternal) + } +} + +func TestIsInternal(t *testing.T) { + f := NewInternalFunction("isInternal") + if !f.IsInternal { + t.Fatalf("expected f.IsInternal to be true, got %t", f.IsInternal) + } +} + func TestInputRegisters(t *testing.T) { cases := []struct { Name string diff --git a/printer/goasm.go b/printer/goasm.go index 23f5b2f7..104572ef 100644 --- a/printer/goasm.go +++ b/printer/goasm.go @@ -67,12 +67,22 @@ func (p *goasm) includes(paths []string) { func (p *goasm) function(f *ir.Function) { p.NL() - p.Comment(f.Stub()) + if !f.IsInternal { + p.Comment(f.Stub()) + } else { + p.Comment("Internal Function (not linked to any package)") + } if len(f.ISA) > 0 { p.Comment("Requires: " + strings.Join(f.ISA, ", ")) } + if f.IsInternal { + p.Printf("TEXT %s(SB)", f.Name) + } else { + p.Printf("TEXT %s%s(SB)", dot, f.Name) + } + // Reference: https://github.com/golang/go/blob/b115207baf6c2decc3820ada4574ef4e5ad940ec/src/cmd/internal/obj/util.go#L166-L176 // // if p.As == ATEXT { @@ -87,7 +97,6 @@ func (p *goasm) function(f *ir.Function) { // } // } // - p.Printf("TEXT %s%s(SB)", dot, f.Name) if f.Attributes != 0 { p.Printf(", %s", f.Attributes.Asm()) } diff --git a/printer/goasm_test.go b/printer/goasm_test.go index e8b4fe1e..877efbd2 100644 --- a/printer/goasm_test.go +++ b/printer/goasm_test.go @@ -1,6 +1,8 @@ package printer_test import ( + "errors" + "strings" "testing" "github.com/mmcloughlin/avo/attr" @@ -34,6 +36,43 @@ func TestBasic(t *testing.T) { }) } +func TestInternal(t *testing.T) { + ctx := build.NewContext() + ctx.InternalFunction("internal") + ctx.AllocLocal(16) + ctx.RET() + + AssertPrintsLines(t, ctx, printer.NewGoAsm, []string{ + "// Code generated by avo. DO NOT EDIT.", + "", + "// Internal Function (not linked to any package)", + "TEXT internal(SB), $16", + "\tRET", + "", + }) +} + +func TestInternalSigExp(t *testing.T) { + ctx := build.NewContext() + ctx.InternalFunction("internal") + ctx.SignatureExpr("invalidSig(b bool)") + ctx.ADDQ(reg.EAX, reg.ECX) + + _, err := ctx.Result() + expect := "cannot link SignatureExpr \"invalidSig(b bool)\" with InternalFunction \"internal\"" + + var list build.ErrorList + if errors.As(err, &list) { + for _, e := range list { + if strings.Contains(e.Error(), expect) { + return + } + } + } + + t.Fatal("Calling SignatureExpr() while the current function is an InternalFunction should generate an error") +} + func TestTextDecl(t *testing.T) { ctx := build.NewContext()