Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions internal/directive/directive.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

// Package directive parses Go and LLGo source directives without assigning
// feature-specific semantics to them.
package directive

import (
"go/ast"
"go/token"
"strings"
)

// Directive is one normalized Go or LLGo source directive.
type Directive struct {
Name string
Args string
Raw string
Pos token.Pos
}

// Parse normalizes comment when it uses a supported directive spelling.
func Parse(comment *ast.Comment) (Directive, bool) {
if comment == nil {
return Directive{}, false
}
raw := comment.Text
var namespace, body string
switch {
case strings.HasPrefix(raw, "//go:"):
namespace, body = "go:", raw[len("//go:"):]
case strings.HasPrefix(raw, "//llgo:"):
namespace, body = "llgo:", raw[len("//llgo:"):]
case strings.HasPrefix(raw, "// llgo:"):
namespace, body = "llgo:", raw[len("// llgo:"):]
case strings.HasPrefix(raw, "//export "):
return Directive{Name: "export", Args: strings.TrimSpace(raw[len("//export "):]), Raw: raw, Pos: comment.Pos()}, true
default:
return Directive{}, false
}
body = strings.TrimSpace(body)
if body == "" {
return Directive{}, false
}
name, args := body, ""
if idx := strings.IndexAny(body, " \t"); idx >= 0 {
name, args = body[:idx], strings.TrimSpace(body[idx+1:])
}
return Directive{Name: namespace + name, Args: args, Raw: raw, Pos: comment.Pos()}, true
}

// ParseGroup returns all normalized directives in doc in source order.
func ParseGroup(doc *ast.CommentGroup) []Directive {
if doc == nil {
return nil
}
ret := make([]Directive, 0, len(doc.List))
for _, comment := range doc.List {
if parsed, ok := Parse(comment); ok {
ret = append(ret, parsed)
}
}
return ret
}
47 changes: 47 additions & 0 deletions internal/directive/directive_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package directive

import (
"go/ast"
"testing"
)

func TestParse(t *testing.T) {
tests := []struct {
text string
name string
args string
ok bool
}{
{text: "// ordinary"},
{text: "//go:"},
{text: "//go:noinline", name: "go:noinline", ok: true},
{text: "//llgo:tls", name: "llgo:tls", ok: true},
{text: "// llgo:type C", name: "llgo:type", args: "C", ok: true},
{text: "//llgo:link\tF C.f", name: "llgo:link", args: "F C.f", ok: true},
{text: "//export F", name: "export", args: "F", ok: true},
}
if _, ok := Parse(nil); ok {
t.Fatal("nil comment parsed as a directive")
}
if ParseGroup(nil) != nil {
t.Fatal("nil comment group returned directives")
}
for _, test := range tests {
got, ok := Parse(&ast.Comment{Text: test.text})
if ok != test.ok || got.Name != test.name || got.Args != test.args || got.Raw != map[bool]string{true: test.text}[test.ok] {
t.Fatalf("Parse(%q) = %+v, %v", test.text, got, ok)
}
}
}

func TestParseGroupPreservesSourceOrder(t *testing.T) {
doc := &ast.CommentGroup{List: []*ast.Comment{
{Text: "// ordinary"},
{Text: "//go:noinline"},
{Text: "//llgo:tls"},
}}
got := ParseGroup(doc)
if len(got) != 2 || got[0].Name != "go:noinline" || got[1].Name != "llgo:tls" {
t.Fatalf("ParseGroup = %+v", got)
}
}
90 changes: 90 additions & 0 deletions internal/locality/locality.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

// Package locality defines the source-level TLS and GLS declaration model.
// It intentionally has no dependency on LLGo's SSA or LLVM lowering layers.
package locality

import "fmt"

const (
ThreadDirective = "//llgo:tls"
GoroutineDirective = "//llgo:gls"
InitPrefix = "__llgo_local_init_"
)

// Kind identifies the execution context that owns a package variable.
type Kind uint8

const (
None Kind = iota
Thread
Goroutine
)

// Info is the locality-specific part of a declaration's compiler metadata.
type Info struct {
Locality Kind
HasInitializer bool
InitFunc string
InitOrder int
}

func (kind Kind) String() string {
switch kind {
case None:
return ""
case Thread:
return "tls"
case Goroutine:
return "gls"
default:
return fmt.Sprintf("invalid:%d", kind)
}
}

// Parse converts the cache representation of a locality into a Kind.
func Parse(name string) (Kind, bool) {
switch name {
case "":
return None, true
case "tls":
return Thread, true
case "gls":
return Goroutine, true
default:
return None, false
}
}

// Directive returns the source directive for kind.
func Directive(kind Kind) string {
if kind == Goroutine {
return GoroutineDirective
}
return ThreadDirective
}

// Merge combines declaration- and spec-level locality directives.
func Merge(a, b Kind) (Kind, bool) {
if a != None && b != None && a != b {
return None, false
}
if b != None {
return b, true
}
return a, true
}
Loading
Loading