Repository: tetratelabs/wazero
Pull request #281 (2022-02-23 09:01:17)

Adds ModuleConfig.Validate() to allow pre-flight checks on source
This adds `ModuleConfig.Validate()` as needed by wapc-go to ensure a
module instantiated many times later is checked first. This is a method
of config in case we later add the ability to force a format. For now,
it relies on detection and the call-site only use binary anyway.


codefromthecrypt: ohh if we are validating we can cache the decode results and improve perf. I will revise one sec!
codefromthecrypt: PTAL this isn't the first place I've seen the same wasm used many times for different modules. It may be the case later that we actually pre-compile it and solve #179 
codefromthecrypt: Thanks again for coming out of holiday @mathetake!

Patch:
diff --git a/wasm.go b/wasm.go
index 49763832..acfdab11 100644
--- a/wasm.go
+++ b/wasm.go
@@ -65,6 +65,17 @@ type ModuleConfig struct {
 	Name string
 	// Source is the WebAssembly 1.0 (MVP) text or binary encoding of the module.
 	Source []byte
+
+	validatedSource []byte
+	decodedModule   *internalwasm.Module
+}
+
+// Validate eagerly decodes the Source and errs if it is invalid.
+//
+// This is used to pre-flight check and cache the module for later instantiation.
+func (m *ModuleConfig) Validate() (err error) {
+	_, _, err = decodeModule(m)
+	return err
 }
 
 // InstantiateModule instantiates the module namespace or errs if the configuration was invalid.
@@ -96,6 +107,12 @@ func decodeModule(module *ModuleConfig) (m *internalwasm.Module, name string, er
 		return
 	}
 
+	// Check if this source was already decoded
+	if bytes.Equal(module.Source, module.validatedSource) {
+		m = module.decodedModule
+		return
+	}
+
 	// Peek to see if this is a binary or text format
 	if bytes.Equal(module.Source[0:4], binary.Magic) {
 		m, err = binary.DecodeModule(module.Source)
@@ -106,6 +123,10 @@ func decodeModule(module *ModuleConfig) (m *internalwasm.Module, name string, er
 		return
 	}
 
+	// Cache as tools like wapc-go re-instantiate the same module many times.
+	module.validatedSource = module.Source
+	module.decodedModule = m
+
 	name = module.Name
 	if name == "" && m.NameSection != nil {
 		name = m.NameSection.ModuleName


Test patch:
diff --git a/wasm_test.go b/wasm_test.go
index 4875e4dc..0231d6c4 100644
--- a/wasm_test.go
+++ b/wasm_test.go
@@ -34,15 +34,43 @@ func TestDecodeModule(t *testing.T) {
 		tc := tt
 
 		t.Run(tc.name, func(t *testing.T) {
-			_, name, err := decodeModule(&ModuleConfig{Name: tc.moduleName, Source: tc.source})
+			config := &ModuleConfig{Name: tc.moduleName, Source: tc.source}
+			_, name, err := decodeModule(config)
 			require.NoError(t, err)
 			if tc.moduleName == "" {
 				require.Equal(t, "test" /* from the text format */, name)
 			} else {
 				require.Equal(t, tc.moduleName, name)
 			}
+
+			// Avoid adding another test just to check Validate works
+			require.NoError(t, config.Validate())
 		})
 	}
+
+	t.Run("caches repetitive decodes", func(t *testing.T) {
+		config := &ModuleConfig{Source: wat}
+		m, _, err := decodeModule(config)
+		require.NoError(t, err)
+
+		again, _, err := decodeModule(config)
+		require.NoError(t, err)
+
+		require.Same(t, m, again)
+	})
+
+	t.Run("changing source invalidates decode cache", func(t *testing.T) {
+		config := &ModuleConfig{Source: wat}
+		m, _, err := decodeModule(config)
+		require.NoError(t, err)
+
+		config.Source = wasm
+		again, _, err := decodeModule(config)
+		require.NoError(t, err)
+
+		require.Equal(t, m, again)
+		require.NotSame(t, m, again)
+	})
 }
 
 func TestDecodeModule_Errors(t *testing.T) {
@@ -71,8 +99,12 @@ func TestDecodeModule_Errors(t *testing.T) {
 		tc := tt
 
 		t.Run(tc.name, func(t *testing.T) {
-			_, _, err := decodeModule(&ModuleConfig{Source: tc.source})
+			config := &ModuleConfig{Source: tc.source}
+			_, _, err := decodeModule(config)
 			require.EqualError(t, err, tc.expectedErr)
+
+			// Avoid adding another test just to check Validate works
+			require.EqualError(t, config.Validate(), tc.expectedErr)
 		})
 	}
 }