Repository: tetratelabs/wazero (go)

Issue:
Expose more information from CompiledModule
**Is your feature request related to a problem? Please describe.**
After having created a `CompiledModule`, I'd like to infer some information from it. For example, I'd like to know what type of functions it wants imported and what kind of functions it is exporting, amongst other things.

**Describe the solution you'd like**
`CompiledModule` (or actually, it's underlaying struct `compiledModule{}`) seems to embed `internal/wasm.Module{}` which appears to hold that information. Perhaps we could bubble up that information to the public API?

**Describe alternatives you've considered**
What I can do right now is create an instance from the `CompiledModule` and offer it all the imports available to me in the hope that it satisfies what the module needs. Then I can poke around with functions like `ExportedFunction()` and poll for information.

Obviously, this is not a great solution and this solution doesn't help me figure out what imports a module needs.


Pull request:
Adds ExportedFunctions API on CompiledModule.
Closes #637 

Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>

Patch:
diff --git a/api/wasm.go b/api/wasm.go
index 118f3c1d..87c12b75 100644
--- a/api/wasm.go
+++ b/api/wasm.go
@@ -173,7 +173,28 @@ type Closer interface {
 	Close(context.Context) error
 }
 
-// Function is a WebAssembly 1.0 (20191205) function exported from an instantiated module (wazero.Runtime InstantiateModule).
+// ExportedFunction is a WebAssembly function exported in a module (wazero.CompiledModule).
+//
+// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#exports%E2%91%A0
+type ExportedFunction interface {
+	// Name returns the name of this exported function which is unique across a module.
+	// Note: the empty name is allowed in the WebAssembly specification so returned "" is meaningful.
+	Name() string
+
+	// ParamTypes are the possibly empty sequence of value types accepted by a function with this signature.
+	//
+	// See ValueType documentation for encoding rules.
+	ParamTypes() []ValueType
+
+	// ResultTypes are the possibly empty sequence of value types returned by a function with this signature.
+	//
+	// When WebAssembly 1.0 (20191205), there can be at most one result: https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#result-types%E2%91%A0
+	//
+	// See ValueType documentation for decoding rules.
+	ResultTypes() []ValueType
+}
+
+// Function is a WebAssembly function exported from an instantiated module (wazero.Runtime InstantiateModule).
 //
 // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-func
 type Function interface {
diff --git a/config.go b/config.go
index 9b9ef31e..fdfa384b 100644
--- a/config.go
+++ b/config.go
@@ -268,6 +268,9 @@ type CompiledModule interface {
 	//
 	// Note: It is safe to call Close while having outstanding calls from an api.Module instantiated from this.
 	Close(context.Context) error
+
+	// ExportedFunctions returns all the exported functions (api.ExportedFunction) in this module.
+	ExportedFunctions() []api.ExportedFunction
 }
 
 type compiledModule struct {
@@ -288,6 +291,11 @@ func (c *compiledModule) Close(_ context.Context) error {
 	return nil
 }
 
+// ExportedFunctions implements CompiledModule.ExportedFunctions
+func (c *compiledModule) ExportedFunctions() []api.ExportedFunction {
+	return c.module.ExportedFunctions()
+}
+
 // CompileConfig allows you to override what was decoded from wasm, prior to compilation (ModuleBuilder.Compile or
 // Runtime.CompileModule).
 //
diff --git a/internal/wasm/module_exports.go b/internal/wasm/module_exports.go
new file mode 100644
index 00000000..352b057c
--- /dev/null
+++ b/internal/wasm/module_exports.go
@@ -0,0 +1,41 @@
+package wasm
+
+import "github.com/tetratelabs/wazero/api"
+
+// ExportedFunctions returns the implementations of api.ExportedFunction.
+func (m *Module) ExportedFunctions() (ret []api.ExportedFunction) {
+	for _, exp := range m.ExportSection {
+		if exp.Type == ExternTypeFunc {
+			tp := m.TypeOfFunction(exp.Index)
+			ret = append(ret, &exportedFunction{
+				exportedName: exp.Name,
+				params:       tp.Params,
+				results:      tp.Results,
+			})
+		}
+	}
+	return
+}
+
+// exportedFunction implements api.ExportedFunction
+type exportedFunction struct {
+	// exportedName is the name of export entry of this function,
+	// which might differ from the one in the name custom section etc.
+	exportedName    string
+	params, results []ValueType
+}
+
+// Name implements api.ExportedFunction Name.
+func (e *exportedFunction) Name() string {
+	return e.exportedName
+}
+
+// ParamTypes implements api.ExportedFunction ParamTypes.
+func (e *exportedFunction) ParamTypes() []ValueType {
+	return e.params
+}
+
+// ResultTypes implements api.ExportedFunction ResultTypes.
+func (e *exportedFunction) ResultTypes() []ValueType {
+	return e.results
+}


Test patch:
diff --git a/internal/wasm/module_exports_test.go b/internal/wasm/module_exports_test.go
new file mode 100644
index 00000000..71f3d52c
--- /dev/null
+++ b/internal/wasm/module_exports_test.go
@@ -0,0 +1,113 @@
+package wasm
+
+import (
+	"testing"
+
+	"github.com/tetratelabs/wazero/api"
+	"github.com/tetratelabs/wazero/internal/testing/require"
+)
+
+func TestModule_ExportedFunctions(t *testing.T) {
+	tests := []struct {
+		name string
+		m    *Module
+		exp  []api.ExportedFunction
+	}{
+		{
+			name: "no exports",
+			m:    &Module{},
+		},
+		{
+			name: "no functions",
+			m: &Module{
+				ExportSection: []*Export{{Type: ExternTypeGlobal, Index: 0}},
+				GlobalSection: []*Global{{}},
+			},
+		},
+		{
+			name: "without imports",
+			m: &Module{
+				ExportSection: []*Export{
+					{Name: "function_index=0", Type: ExternTypeFunc, Index: 0},
+					{Name: "function_index=2", Type: ExternTypeFunc, Index: 2},
+					{Name: "", Type: ExternTypeGlobal, Index: 0},
+					{Name: "function_index=1", Type: ExternTypeFunc, Index: 1},
+				},
+				GlobalSection:   []*Global{{}},
+				FunctionSection: []Index{1, 2, 0},
+				TypeSection: []*FunctionType{
+					{Params: []ValueType{}, Results: []ValueType{}},
+					{Params: []ValueType{ValueTypeF64, ValueTypeI32}, Results: []ValueType{ValueTypeV128, ValueTypeI64}},
+					{Params: []ValueType{ValueTypeF64, ValueTypeF32}, Results: []ValueType{ValueTypeI64}},
+				},
+			},
+			exp: []api.ExportedFunction{
+				&exportedFunction{
+					exportedName: "function_index=0",
+					params:       []ValueType{ValueTypeF64, ValueTypeI32},
+					results:      []ValueType{ValueTypeV128, ValueTypeI64},
+				},
+				&exportedFunction{
+					exportedName: "function_index=2", params: []ValueType{}, results: []ValueType{},
+				},
+				&exportedFunction{
+					exportedName: "function_index=1",
+					params:       []ValueType{ValueTypeF64, ValueTypeF32},
+					results:      []ValueType{ValueTypeI64},
+				},
+			},
+		},
+		{
+			name: "with imports",
+			m: &Module{
+				ImportSection: []*Import{{
+					Type:     ExternTypeFunc,
+					DescFunc: 2, // Index of type.
+				}},
+				ExportSection: []*Export{
+					{Name: "imported_function", Type: ExternTypeFunc, Index: 0},
+					{Name: "function_index=1", Type: ExternTypeFunc, Index: 1},
+					{Name: "function_index=2", Type: ExternTypeFunc, Index: 2},
+				},
+				FunctionSection: []Index{1, 0},
+				TypeSection: []*FunctionType{
+					{Params: []ValueType{}, Results: []ValueType{}},
+					{Params: []ValueType{ValueTypeF64, ValueTypeI32}, Results: []ValueType{ValueTypeV128, ValueTypeI64}},
+					{Params: []ValueType{ValueTypeF64, ValueTypeF32}, Results: []ValueType{ValueTypeI64}},
+				},
+			},
+			exp: []api.ExportedFunction{
+				&exportedFunction{
+					exportedName: "imported_function",
+					params:       []ValueType{ValueTypeF64, ValueTypeF32},
+					results:      []ValueType{ValueTypeI64},
+				},
+				&exportedFunction{
+					exportedName: "function_index=1",
+					params:       []ValueType{ValueTypeF64, ValueTypeI32},
+					results:      []ValueType{ValueTypeV128, ValueTypeI64},
+				},
+				&exportedFunction{
+					exportedName: "function_index=2",
+					params:       []ValueType{},
+					results:      []ValueType{},
+				},
+			},
+		},
+	}
+
+	for _, tc := range tests {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			actual := tc.m.ExportedFunctions()
+			require.Equal(t, tc.exp, actual)
+		})
+	}
+}
+
+func TestExportedFunction(t *testing.T) {
+	f := &exportedFunction{exportedName: "abc", params: []ValueType{ValueTypeI32}, results: []ValueType{ValueTypeV128}}
+	require.Equal(t, "abc", f.exportedName)
+	require.Equal(t, []ValueType{ValueTypeI32}, f.ParamTypes())
+	require.Equal(t, []ValueType{ValueTypeV128}, f.ResultTypes())
+}