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()