Repository: tetratelabs/wazero
Pull request #698 (2022-07-14 09:21:01)

Makes ExportedFunctions unique
Exported functions are easier to use as a map vs making the callers do
it.


codefromthecrypt: here's the call site from dapr

```go
// preflightCheckWasm check that the wasm contains functions exported by TinyGo.
func (rh *wazeroRequestHandler) preflightCheckWasm() error {
	exportedFunctions := rh.compiled.ExportedFunctions()

	for _, fn := range []string{wasmFuncMalloc, wasmFuncFree, wasmFuncRewrite} {
		if _, ok := exportedFunctions[fn]; !ok {
			return fmt.Errorf("module[%s] didn't export function[%s]", rh.compiled.Name(), fn)
		}
	}

	return nil
}
```

Patch:
diff --git a/config.go b/config.go
index 51b5b6ab..dd90a444 100644
--- a/config.go
+++ b/config.go
@@ -267,15 +267,24 @@ func (c *runtimeConfig) WithWasmCore2() RuntimeConfig {
 //
 // Note: Closing the wazero.Runtime closes any CompiledModule it compiled.
 type CompiledModule interface {
-	// ImportedFunctions returns all the imported functions (api.FunctionDefinition) in this module.
+	// Name returns the module name encoded into the binary or empty if not.
+	Name() string
+
+	// ImportedFunctions returns all the imported functions
+	// (api.FunctionDefinition) in this module or nil if there are none.
+	//
+	// Note: Unlike ExportedFunctions, there is no unique constraint on
+	// imports.
 	ImportedFunctions() []api.FunctionDefinition
 
-	// ExportedFunctions returns all the exported functions (api.FunctionDefinition) in this module.
-	ExportedFunctions() []api.FunctionDefinition
+	// ExportedFunctions returns all the exported functions
+	// (api.FunctionDefinition) in this module keyed on export name.
+	ExportedFunctions() map[string]api.FunctionDefinition
 
 	// Close releases all the allocated resources for this CompiledModule.
 	//
-	// Note: It is safe to call Close while having outstanding calls from an api.Module instantiated from this.
+	// Note: It is safe to call Close while having outstanding calls from an
+	// api.Module instantiated from this.
 	Close(context.Context) error
 }
 
@@ -289,6 +298,14 @@ type compiledModule struct {
 	closeWithModule bool
 }
 
+// Name implements CompiledModule.Name
+func (c *compiledModule) Name() (moduleName string) {
+	if ns := c.module.NameSection; ns != nil {
+		moduleName = ns.ModuleName
+	}
+	return
+}
+
 // Close implements CompiledModule.Close
 func (c *compiledModule) Close(_ context.Context) error {
 	// Note: If you use the context.Context param, don't forget to coerce nil to context.Background()!
@@ -304,7 +321,7 @@ func (c *compiledModule) ImportedFunctions() []api.FunctionDefinition {
 }
 
 // ExportedFunctions implements CompiledModule.ExportedFunctions
-func (c *compiledModule) ExportedFunctions() []api.FunctionDefinition {
+func (c *compiledModule) ExportedFunctions() map[string]api.FunctionDefinition {
 	return c.module.ExportedFunctions()
 }
 
diff --git a/experimental/log_listener.go b/experimental/log_listener.go
index 202a6260..94c1d43d 100644
--- a/experimental/log_listener.go
+++ b/experimental/log_listener.go
@@ -12,7 +12,7 @@ import (
 )
 
 // NewLoggingListenerFactory implements FunctionListenerFactory to log all
-// function calls to the writer.
+// functions that have a name to the writer.
 func NewLoggingListenerFactory(writer io.Writer) FunctionListenerFactory {
 	return &loggingListenerFactory{writer}
 }
diff --git a/internal/wasm/function_definition.go b/internal/wasm/function_definition.go
index 281fe790..45d6c6dc 100644
--- a/internal/wasm/function_definition.go
+++ b/internal/wasm/function_definition.go
@@ -6,6 +6,8 @@ import (
 )
 
 // ImportedFunctions returns the definitions of each imported function.
+//
+// Note: Unlike ExportedFunctions, there is no unique constraint on imports.
 func (m *Module) ImportedFunctions() (ret []api.FunctionDefinition) {
 	for _, d := range m.FunctionDefinitionSection {
 		if d.importDesc != nil {
@@ -16,13 +18,14 @@ func (m *Module) ImportedFunctions() (ret []api.FunctionDefinition) {
 }
 
 // ExportedFunctions returns the definitions of each exported function.
-func (m *Module) ExportedFunctions() (ret []api.FunctionDefinition) {
+func (m *Module) ExportedFunctions() map[string]api.FunctionDefinition {
+	ret := map[string]api.FunctionDefinition{}
 	for _, d := range m.FunctionDefinitionSection {
-		if d.exportNames != nil {
-			ret = append(ret, d)
+		for _, e := range d.exportNames {
+			ret[e] = d
 		}
 	}
-	return
+	return ret
 }
 
 // BuildFunctionDefinitions generates function metadata that can be parsed from


Test patch:
diff --git a/config_test.go b/config_test.go
index c1188c46..a17b2711 100644
--- a/config_test.go
+++ b/config_test.go
@@ -689,6 +689,7 @@ func TestModuleConfig_toSysContext_Errors(t *testing.T) {
 		})
 	}
 }
+
 func TestModuleConfig_clone(t *testing.T) {
 	mc := NewModuleConfig().(*moduleConfig)
 	cloned := mc.clone()
@@ -707,7 +708,37 @@ func TestModuleConfig_clone(t *testing.T) {
 	require.Nil(t, cloned.fs)
 }
 
-func TestCompiledCode_Close(t *testing.T) {
+func Test_compiledModule_Name(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    *compiledModule
+		expected string
+	}{
+		{
+			name:  "no name section",
+			input: &compiledModule{module: &wasm.Module{}},
+		},
+		{
+			name:  "empty name",
+			input: &compiledModule{module: &wasm.Module{NameSection: &wasm.NameSection{}}},
+		},
+		{
+			name:     "name",
+			input:    &compiledModule{module: &wasm.Module{NameSection: &wasm.NameSection{ModuleName: "foo"}}},
+			expected: "foo",
+		},
+	}
+
+	for _, tt := range tests {
+		tc := tt
+
+		t.Run(tc.name, func(t *testing.T) {
+			require.Equal(t, tc.expected, tc.input.Name())
+		})
+	}
+}
+
+func Test_compiledModule_Close(t *testing.T) {
 	for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil!
 		e := &mockEngine{name: "1", cachedModules: map[*wasm.Module]struct{}{}}
 
diff --git a/internal/wasm/function_definition_test.go b/internal/wasm/function_definition_test.go
index 408893ff..9400b043 100644
--- a/internal/wasm/function_definition_test.go
+++ b/internal/wasm/function_definition_test.go
@@ -12,14 +12,16 @@ func TestModule_BuildFunctionDefinitions(t *testing.T) {
 	nopCode := &Code{nil, []byte{OpcodeEnd}}
 	fnV := reflect.ValueOf(func() {})
 	tests := []struct {
-		name                             string
-		m                                *Module
-		expected                         []*FunctionDefinition
-		expectedImports, expectedExports []api.FunctionDefinition
+		name            string
+		m               *Module
+		expected        []*FunctionDefinition
+		expectedImports []api.FunctionDefinition
+		expectedExports map[string]api.FunctionDefinition
 	}{
 		{
-			name: "no exports",
-			m:    &Module{},
+			name:            "no exports",
+			m:               &Module{},
+			expectedExports: map[string]api.FunctionDefinition{},
 		},
 		{
 			name: "no functions",
@@ -27,6 +29,7 @@ func TestModule_BuildFunctionDefinitions(t *testing.T) {
 				ExportSection: []*Export{{Type: ExternTypeGlobal, Index: 0}},
 				GlobalSection: []*Global{{}},
 			},
+			expectedExports: map[string]api.FunctionDefinition{},
 		},
 		{
 			name: "host func",
@@ -43,6 +46,7 @@ func TestModule_BuildFunctionDefinitions(t *testing.T) {
 					funcType:       v_v,
 				},
 			},
+			expectedExports: map[string]api.FunctionDefinition{},
 		},
 		{
 			name: "without imports",
@@ -81,20 +85,20 @@ func TestModule_BuildFunctionDefinitions(t *testing.T) {
 					funcType:    v_v,
 				},
 			},
-			expectedExports: []api.FunctionDefinition{
-				&FunctionDefinition{
+			expectedExports: map[string]api.FunctionDefinition{
+				"function_index=0": &FunctionDefinition{
 					index:       0,
 					debugName:   ".$0",
 					exportNames: []string{"function_index=0"},
 					funcType:    &FunctionType{Params: []ValueType{ValueTypeF64, ValueTypeI32}, Results: []ValueType{ValueTypeV128, ValueTypeI64}},
 				},
-				&FunctionDefinition{
+				"function_index=1": &FunctionDefinition{
 					index:       1,
 					exportNames: []string{"function_index=1"},
 					debugName:   ".$1",
 					funcType:    &FunctionType{Params: []ValueType{ValueTypeF64, ValueTypeF32}, Results: []ValueType{ValueTypeI64}},
 				},
-				&FunctionDefinition{
+				"function_index=2": &FunctionDefinition{
 					index:       2,
 					debugName:   ".$2",
 					exportNames: []string{"function_index=2"},
@@ -151,21 +155,21 @@ func TestModule_BuildFunctionDefinitions(t *testing.T) {
 					funcType:    &FunctionType{Params: []ValueType{ValueTypeF64, ValueTypeF32}, Results: []ValueType{ValueTypeI64}},
 				},
 			},
-			expectedExports: []api.FunctionDefinition{
-				&FunctionDefinition{
+			expectedExports: map[string]api.FunctionDefinition{
+				"imported_function": &FunctionDefinition{
 					index:       0,
 					debugName:   ".$0",
 					importDesc:  &[2]string{"", ""},
 					exportNames: []string{"imported_function"},
 					funcType:    &FunctionType{Params: []ValueType{ValueTypeF64, ValueTypeF32}, Results: []ValueType{ValueTypeI64}},
 				},
-				&FunctionDefinition{
+				"function_index=1": &FunctionDefinition{
 					index:       1,
 					debugName:   ".$1",
 					exportNames: []string{"function_index=1"},
 					funcType:    &FunctionType{Params: []ValueType{ValueTypeF64, ValueTypeI32}, Results: []ValueType{ValueTypeV128, ValueTypeI64}},
 				},
-				&FunctionDefinition{
+				"function_index=2": &FunctionDefinition{
 					index:       2,
 					debugName:   ".$2",
 					exportNames: []string{"function_index=2"},
@@ -200,6 +204,7 @@ func TestModule_BuildFunctionDefinitions(t *testing.T) {
 			expectedImports: []api.FunctionDefinition{
 				&FunctionDefinition{moduleName: "module", index: 0, debugName: "module.$0", importDesc: &[2]string{"i", "f"}, funcType: v_v},
 			},
+			expectedExports: map[string]api.FunctionDefinition{},
 		},
 	}