Skip to content

Commit bd4f87f

Browse files
committed
wit, cmd/wit-bindgen-go: WIT tree shaking
This enables WIT tree-shaking by world or interface, as a precursor for Go package-level component metadata.
1 parent b98bdef commit bd4f87f

28 files changed

+1933
-2349
lines changed

cmd/wit-bindgen-go/cmd/wit/wit.go

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ var Command = &cli.Command{
2323
Config: cli.StringConfig{TrimSpace: true},
2424
Usage: "WIT world to generate, otherwise generate all worlds",
2525
},
26+
&cli.StringFlag{
27+
Name: "interface",
28+
Aliases: []string{"i"},
29+
Value: "",
30+
OnlyOnce: true,
31+
Config: cli.StringConfig{TrimSpace: true},
32+
Usage: "WIT interface to generate, otherwise generate all interfaces",
33+
},
2634
},
2735
Action: action,
2836
}
@@ -32,19 +40,30 @@ func action(ctx context.Context, cmd *cli.Command) error {
3240
if err != nil {
3341
return err
3442
}
43+
3544
res, err := witcli.LoadWIT(ctx, path, cmd.Reader, cmd.Bool("force-wit"))
3645
if err != nil {
3746
return err
3847
}
48+
3949
var w *wit.World
40-
world := cmd.String("world")
41-
if world != "" {
50+
if world := cmd.String("world"); world != "" {
4251
w = findWorld(res, world)
4352
if w == nil {
4453
return fmt.Errorf("world %s not found", world)
4554
}
4655
}
47-
fmt.Print(res.WIT(w, ""))
56+
57+
var i *wit.Interface
58+
if face := cmd.String("interface"); face != "" {
59+
i = findInterface(res, face)
60+
if i == nil {
61+
return fmt.Errorf("interface %s not found", face)
62+
}
63+
}
64+
65+
filter := wit.Filter(w, i)
66+
fmt.Print(res.WIT(filter, ""))
4867
return nil
4968
}
5069

@@ -56,3 +75,12 @@ func findWorld(r *wit.Resolve, pattern string) *wit.World {
5675
}
5776
return nil
5877
}
78+
79+
func findInterface(r *wit.Resolve, pattern string) *wit.Interface {
80+
for _, i := range r.Interfaces {
81+
if i.Match(pattern) {
82+
return i
83+
}
84+
}
85+
return nil
86+
}

internal/memoize/memoize.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package memoize
2+
3+
// Function memoizes f, caching unique values of k.
4+
// Initial calls to the resulting function will call f(k), then cache and return v.
5+
// Subsequent calls will return the cached value for k.
6+
func Function[F func(K) V, K comparable, V any](f F) F {
7+
m := make(map[K]V)
8+
return func(k K) V {
9+
if v, ok := m[k]; ok {
10+
return v
11+
}
12+
v := f(k)
13+
m[k] = v
14+
return v
15+
}
16+
}

wit/bindgen/generator.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ func (g *generator) defineWorld(w *wit.World) error {
232232

233233
// Write WIT file for this world
234234
witFile := g.witFileFor(w)
235-
witFile.WriteString(g.res.WIT(w, ""))
235+
witFile.WriteString(g.res.WIT(wit.Filter(w, nil), ""))
236236

237237
// Write Go package docs
238238
file := g.fileFor(w)

wit/docs.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,8 @@
4141
// [WebAssembly Interface Type]: https://component-model.bytecodealliance.org/design/wit.html
4242
// [Component]: https://component-model.bytecodealliance.org/introduction.html
4343
package wit
44+
45+
// Docs represent WIT documentation text extracted from comments.
46+
type Docs struct {
47+
Contents string // may be empty
48+
}

wit/enum.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package wit
2+
3+
// Enum represents a WIT [enum type], which is a [Variant] without associated data.
4+
// The equivalent in Go is a set of const identifiers declared with iota.
5+
// It implements the [Node], [ABI], and [TypeDefKind] interfaces.
6+
//
7+
// [enum type]: https://component-model.bytecodealliance.org/design/wit.html#enums
8+
type Enum struct {
9+
_typeDefKind
10+
Cases []EnumCase
11+
}
12+
13+
// Despecialize despecializes [Enum] e into a [Variant] with no associated types.
14+
// See the [canonical ABI documentation] for more information.
15+
//
16+
// [canonical ABI documentation]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#despecialization
17+
func (e *Enum) Despecialize() TypeDefKind {
18+
v := &Variant{
19+
Cases: make([]Case, len(e.Cases)),
20+
}
21+
for i := range e.Cases {
22+
v.Cases[i].Name = e.Cases[i].Name
23+
v.Cases[i].Docs = e.Cases[i].Docs
24+
}
25+
return v
26+
}
27+
28+
// Size returns the [ABI byte size] for [Enum] e, the smallest integer
29+
// type that can represent 0...len(e.Cases).
30+
// It is first [despecialized] into a [Variant] with no associated types, then sized.
31+
//
32+
// [ABI byte size]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#size
33+
// [despecialized]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#despecialization
34+
func (e *Enum) Size() uintptr {
35+
return e.Despecialize().Size()
36+
}
37+
38+
// Align returns the [ABI byte alignment] for [Enum] e.
39+
// It is first [despecialized] into a [Variant] with no associated types, then aligned.
40+
//
41+
// [ABI byte alignment]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#alignment
42+
// [despecialized]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#despecialization
43+
func (e *Enum) Align() uintptr {
44+
return e.Despecialize().Align()
45+
}
46+
47+
// Flat returns the [flattened] ABI representation of [Enum] e.
48+
//
49+
// [flattened]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#flattening
50+
func (v *Enum) Flat() []Type {
51+
return Discriminant(len(v.Cases)).Flat()
52+
}
53+
54+
// EnumCase represents a single case in an [Enum].
55+
// It implements the [Node] interface.
56+
type EnumCase struct {
57+
Name string
58+
Docs Docs
59+
}

wit/flags.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package wit
2+
3+
// Flags represents a WIT [flags type], stored as a bitfield.
4+
// It implements the [Node], [ABI], and [TypeDefKind] interfaces.
5+
//
6+
// [flags type]: https://component-model.bytecodealliance.org/design/wit.html#flags
7+
type Flags struct {
8+
_typeDefKind
9+
Flags []Flag
10+
}
11+
12+
// Size returns the [ABI byte size] of [Flags] f.
13+
//
14+
// [ABI byte size]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#size
15+
func (f *Flags) Size() uintptr {
16+
n := len(f.Flags)
17+
switch {
18+
case n <= 8:
19+
return 1
20+
case n <= 16:
21+
return 2
22+
}
23+
return 4 * uintptr((n+31)>>5)
24+
}
25+
26+
// Align returns the [ABI byte alignment] of [Flags] f.
27+
//
28+
// [ABI byte alignment]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#alignment
29+
func (f *Flags) Align() uintptr {
30+
n := len(f.Flags)
31+
switch {
32+
case n <= 8:
33+
return 1
34+
case n <= 16:
35+
return 2
36+
}
37+
return 4
38+
}
39+
40+
// Flat returns the [flattened] ABI representation of [Flags] f.
41+
//
42+
// [flattened]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#flattening
43+
func (f *Flags) Flat() []Type {
44+
flat := make([]Type, (len(f.Flags)+31)>>5)
45+
for i := range flat {
46+
flat[i] = U32{}
47+
}
48+
return flat
49+
}
50+
51+
// Flag represents a single flag value in a [Flags] type.
52+
// It implements the [Node] interface.
53+
type Flag struct {
54+
Name string
55+
Docs Docs
56+
}

wit/function.go

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
package wit
2+
3+
import (
4+
"strings"
5+
)
6+
7+
// Function represents a WIT [function].
8+
// Functions can be freestanding, methods, constructors or static.
9+
// It implements the [Node] and [WorldItem] interfaces.
10+
//
11+
// [function]: https://component-model.bytecodealliance.org/design/wit.html#functions
12+
type Function struct {
13+
_worldItem
14+
Name string
15+
Kind FunctionKind
16+
Params []Param // arguments to the function
17+
Results []Param // a function can have a single anonymous result, or > 1 named results
18+
Stability Stability // WIT @since or @unstable (nil if unknown)
19+
Docs Docs
20+
}
21+
22+
// BaseName returns the base name of [Function] f.
23+
// For static functions, this returns the function name unchanged.
24+
// For constructors, this removes the [constructor] and type prefix.
25+
// For static functions, this removes the [static] and type prefix.
26+
// For methods, this removes the [method] and type prefix.
27+
// For special functions like [resource-drop], it will return a well-known value.
28+
func (f *Function) BaseName() string {
29+
switch {
30+
case strings.HasPrefix(f.Name, "[constructor]"):
31+
return "constructor"
32+
case strings.HasPrefix(f.Name, "[resource-new]"):
33+
return "resource-new"
34+
case strings.HasPrefix(f.Name, "[resource-rep]"):
35+
return "resource-rep"
36+
case strings.HasPrefix(f.Name, "[resource-drop]"):
37+
return "resource-drop"
38+
case strings.HasPrefix(f.Name, "[dtor]"):
39+
return "destructor"
40+
}
41+
name, after, found := strings.Cut(f.Name, ".")
42+
if found {
43+
name = after
44+
}
45+
after, found = strings.CutPrefix(f.Name, "cabi_post_")
46+
if found {
47+
name = after + "-post-return"
48+
}
49+
return name
50+
}
51+
52+
// Type returns the associated (self) [Type] for [Function] f, if f is a constructor, method, or static function.
53+
// If f is a freestanding function, this returns nil.
54+
func (f *Function) Type() Type {
55+
switch kind := f.Kind.(type) {
56+
case *Constructor:
57+
return kind.Type
58+
case *Static:
59+
return kind.Type
60+
case *Method:
61+
return kind.Type
62+
default:
63+
return nil
64+
}
65+
}
66+
67+
// IsAdmin returns true if [Function] f is an administrative function in the Canonical ABI.
68+
func (f *Function) IsAdmin() bool {
69+
switch {
70+
// Imported
71+
case f.IsStatic() && strings.HasPrefix(f.Name, "[resource-new]"):
72+
return true
73+
case f.IsMethod() && strings.HasPrefix(f.Name, "[resource-rep]"):
74+
return true
75+
case f.IsMethod() && strings.HasPrefix(f.Name, "[resource-drop]"):
76+
return true
77+
78+
// Exported
79+
case f.IsMethod() && strings.HasPrefix(f.Name, "[dtor]"):
80+
return true
81+
case strings.HasPrefix(f.Name, "cabi_post_"):
82+
return true
83+
}
84+
return false
85+
}
86+
87+
// IsFreestanding returns true if [Function] f is a freestanding function,
88+
// and not a constructor, method, or static function.
89+
func (f *Function) IsFreestanding() bool {
90+
_, ok := f.Kind.(*Freestanding)
91+
return ok
92+
}
93+
94+
// IsConstructor returns true if [Function] f is a constructor.
95+
// To qualify, it must have a *[Constructor] Kind with a non-nil type.
96+
func (f *Function) IsConstructor() bool {
97+
kind, ok := f.Kind.(*Constructor)
98+
return ok && kind.Type != nil
99+
}
100+
101+
// IsMethod returns true if [Function] f is a method.
102+
// To qualify, it must have a *[Method] Kind with a non-nil [Type] which matches borrow<t> of its first param.
103+
func (f *Function) IsMethod() bool {
104+
if len(f.Params) == 0 {
105+
return false
106+
}
107+
kind, ok := f.Kind.(*Method)
108+
if !ok {
109+
return false
110+
}
111+
t := f.Params[0].Type
112+
h := KindOf[*Borrow](t)
113+
return t == kind.Type || (h != nil && h.Type == kind.Type)
114+
}
115+
116+
// IsStatic returns true if [Function] f is a static function.
117+
// To qualify, it must have a *[Static] Kind with a non-nil type.
118+
func (f *Function) IsStatic() bool {
119+
kind, ok := f.Kind.(*Static)
120+
return ok && kind.Type != nil
121+
}
122+
123+
func (f *Function) dependsOn(dep Node) bool {
124+
if dep == f {
125+
return true
126+
}
127+
for _, p := range f.Params {
128+
if DependsOn(p.Type, dep) {
129+
return true
130+
}
131+
}
132+
for _, r := range f.Results {
133+
if DependsOn(r.Type, dep) {
134+
return true
135+
}
136+
}
137+
return false
138+
}
139+
140+
func compareFunctions(a, b *Function) int {
141+
return strings.Compare(a.Name, b.Name)
142+
}
143+
144+
// Param represents a parameter to or the result of a [Function].
145+
// A Param can be unnamed.
146+
type Param struct {
147+
Name string
148+
Type Type
149+
}
150+
151+
// FunctionKind represents the kind of a WIT [function], which can be one of
152+
// [Freestanding], [Method], [Static], or [Constructor].
153+
//
154+
// [function]: https://component-model.bytecodealliance.org/design/wit.html#functions
155+
type FunctionKind interface {
156+
isFunctionKind()
157+
}
158+
159+
// _functionKind is an embeddable type that conforms to the [FunctionKind] interface.
160+
type _functionKind struct{}
161+
162+
func (_functionKind) isFunctionKind() {}
163+
164+
// Freestanding represents a free-standing function that is not a method, static, or a constructor.
165+
type Freestanding struct{ _functionKind }
166+
167+
// Method represents a function that is a method on its associated [Type].
168+
// The first argument to the function is self, an instance of [Type].
169+
type Method struct {
170+
_functionKind
171+
Type Type
172+
}
173+
174+
// Static represents a function that is a static method of its associated [Type].
175+
type Static struct {
176+
_functionKind
177+
Type Type
178+
}
179+
180+
// Constructor represents a function that is a constructor for its associated [Type].
181+
type Constructor struct {
182+
_functionKind
183+
Type Type
184+
}

0 commit comments

Comments
 (0)