Repository: tetratelabs/wazero
Pull request #518 (2022-05-02 01:51:03)

Changes RuntimeConfig to an interface and exposes WithWasmCore2
WebAssembly Core Working Draft 1 recently came out. Before that, we had
a toe-hold feature bucked called FinishedFeatures. This replaces
`RuntimeConfig.WithFinishedFeatures` with `RuntimeConfig.WithWasmCore2`.
This also adds `WithWasmCore1` for those who want to lock into 1.0
features as opposed to relying on defaults.

This also fixes some design debt where we hadn't finished migrating
public types that require constructor functions (NewXxx) to interfaces.
By using interfaces, we prevent people from accidentally initializing
key configuration directly (via &Xxx), causing nil field refs. This also
helps prevent confusion about how to use the type (ex pointer or not) as
there's only one way (as an interface).

See https://www.w3.org/blog/news/archives/9509
See https://github.com/tetratelabs/wazero/issues/516

codefromthecrypt: added README about this

Patch:
diff --git a/README.md b/README.md
index 808388a1..bdfbf0fd 100644
--- a/README.md
+++ b/README.md
@@ -203,8 +203,13 @@ as well as how to classify a request for a feature we don't yet support.
 
 ### WebAssembly Core
 wazero conforms with spectests [7] defined alongside WebAssembly Core
-Specification [1.0][1]. There is also [work in progress][14] towards release
-[2.0][2], despite it not being a Web Standard, yet.
+Specification [1.0][1]. This is the default [RuntimeConfig][18].
+
+The WebAssembly Core Specification [2.0][2] is in draft form and wazero has
+[work in progress][14] towards that. Opt in via the below configuration:
+```go
+rConfig = wazero.NewRuntimeConfig().WithWasmCore2()
+```
 
 One current limitation of wazero is that it doesn't fully implement the Text
 Format, yet, e.g. compiling `.wat` files. The intent is to [finish this][15],
diff --git a/config.go b/config.go
index 2a75add9..f7aa199e 100644
--- a/config.go
+++ b/config.go
@@ -16,8 +16,122 @@ import (
 
 // RuntimeConfig controls runtime behavior, with the default implementation as NewRuntimeConfig
 //
+// Ex. To explicitly limit to Wasm Core 1.0 features as opposed to relying on defaults:
+//	rConfig = wazero.NewRuntimeConfig().WithWasmCore1()
+//
 // Note: RuntimeConfig is immutable. Each WithXXX function returns a new instance including the corresponding change.
-type RuntimeConfig struct {
+type RuntimeConfig interface {
+
+	// WithFeatureBulkMemoryOperations adds instructions modify ranges of memory or table entries
+	// ("bulk-memory-operations"). This defaults to false as the feature was not finished in WebAssembly 1.0 (20191205).
+	//
+	// Here are the notable effects:
+	// * Adds `memory.fill`, `memory.init`, `memory.copy` and `data.drop` instructions.
+	// * Adds `table.fill`, `table.init`, `table.copy` and `elem.drop` instructions.
+	// * Introduces a "passive" form of element and data segments.
+	// * Stops checking "active" element and data segment boundaries at compile-time, meaning they can error at runtime.
+	//
+	// Note: "bulk-memory-operations" is mixed with the "reference-types" proposal
+	// due to the WebAssembly Working Group merging them "mutually dependent".
+	// See https://github.com/WebAssembly/spec/blob/main/proposals/bulk-memory-operations/Overview.md
+	// See https://github.com/WebAssembly/spec/blob/main/proposals/reference-types/Overview.md
+	// See https://github.com/WebAssembly/spec/pull/1287
+	WithFeatureBulkMemoryOperations(enabled bool) RuntimeConfig
+
+	// WithFeatureMultiValue enables multiple values ("multi-value"). This defaults to false as the feature was not finished
+	// in WebAssembly 1.0 (20191205).
+	//
+	// Here are the notable effects:
+	// * Function (`func`) types allow more than one result
+	// * Block types (`block`, `loop` and `if`) can be arbitrary function types
+	//
+	// See https://github.com/WebAssembly/spec/blob/main/proposals/multi-value/Overview.md
+	WithFeatureMultiValue(enabled bool) RuntimeConfig
+
+	// WithFeatureMutableGlobal allows globals to be mutable. This defaults to true as the feature was finished in
+	// WebAssembly 1.0 (20191205).
+	//
+	// When false, an api.Global can never be cast to an api.MutableGlobal, and any source that includes global vars
+	// will fail to parse.
+	WithFeatureMutableGlobal(enabled bool) RuntimeConfig
+
+	// WithFeatureNonTrappingFloatToIntConversion enables non-trapping float-to-int conversions.
+	// ("nontrapping-float-to-int-conversion"). This defaults to false as the feature was not in WebAssembly 1.0 (20191205).
+	//
+	// The only effect of enabling this is allowing the following instructions, which return 0 on NaN instead of panicking.
+	// * `i32.trunc_sat_f32_s`
+	// * `i32.trunc_sat_f32_u`
+	// * `i32.trunc_sat_f64_s`
+	// * `i32.trunc_sat_f64_u`
+	// * `i64.trunc_sat_f32_s`
+	// * `i64.trunc_sat_f32_u`
+	// * `i64.trunc_sat_f64_s`
+	// * `i64.trunc_sat_f64_u`
+	//
+	// See https://github.com/WebAssembly/spec/blob/main/proposals/nontrapping-float-to-int-conversion/Overview.md
+	WithFeatureNonTrappingFloatToIntConversion(enabled bool) RuntimeConfig
+
+	// WithFeatureSignExtensionOps enables sign extension instructions ("sign-extension-ops"). This defaults to false as the
+	// feature was not in WebAssembly 1.0 (20191205).
+	//
+	// Here are the notable effects:
+	// * Adds instructions `i32.extend8_s`, `i32.extend16_s`, `i64.extend8_s`, `i64.extend16_s` and `i64.extend32_s`
+	//
+	// See https://github.com/WebAssembly/spec/blob/main/proposals/sign-extension-ops/Overview.md
+	WithFeatureSignExtensionOps(enabled bool) RuntimeConfig
+
+	// WithMemoryCapacityPages is a function that determines memory capacity in pages (65536 bytes per page). The inputs are
+	// the min and possibly nil max defined by the module, and the default is to return the min.
+	//
+	// Ex. To set capacity to max when exists:
+	//	c = c.WithMemoryCapacityPages(func(minPages uint32, maxPages *uint32) uint32 {
+	//		if maxPages != nil {
+	//			return *maxPages
+	//		}
+	//		return minPages
+	//	})
+	//
+	// This function is used at compile time (ModuleBuilder.Build or Runtime.CompileModule). Compile will err if the
+	// function returns a value lower than minPages or greater than WithMemoryLimitPages.
+	WithMemoryCapacityPages(maxCapacityPages func(minPages uint32, maxPages *uint32) uint32) RuntimeConfig
+
+	// WithMemoryLimitPages limits the maximum number of pages a module can define from 65536 pages (4GiB) to a lower value.
+	//
+	// Notes:
+	// * If a module defines no memory max value, Runtime.CompileModule sets max to the limit.
+	// * If a module defines a memory max larger than this limit, it will fail to compile (Runtime.CompileModule).
+	// * Any "memory.grow" instruction that results in a larger value than this results in an error at runtime.
+	// * Zero is a valid value and results in a crash if any module uses memory.
+	//
+	// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#grow-mem
+	// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-types%E2%91%A0
+	WithMemoryLimitPages(memoryLimitPages uint32) RuntimeConfig
+
+	// WithWasmCore1 enables features included in the WebAssembly Core Specification 1.0 (20191205). Selecting this
+	// overwrites any currently accumulated features with only those included in this W3C recommendation.
+	//
+	// This is default because as of mid 2022, this is the only version that is a Web Standard (W3C Recommendation).
+	//
+	// You can select the latest draft of the WebAssembly Core Specification 2.0 instead via WithWasmCore2. You can
+	// also enable or disable individual features via `WithXXX` methods. Ex.
+	//	rConfig = wazero.NewRuntimeConfig().WithWasmCore1().WithFeatureMutableGlobal(false)
+	//
+	// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/
+	WithWasmCore1() RuntimeConfig
+
+	// WithWasmCore2 enables features included in the WebAssembly Core Specification 2.0 (20220419). Selecting this
+	// overwrites any currently accumulated features with only those included in this W3C working draft.
+	//
+	// This is not default because it is not yet incomplete and also not yet a Web Standard (W3C Recommendation).
+	//
+	// Even after selecting this, you can enable or disable individual features via `WithXXX` methods. Ex.
+	//	rConfig = wazero.NewRuntimeConfig().WithWasmCore2().WithFeatureMutableGlobal(false)
+	//
+	// See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/
+	WithWasmCore2() RuntimeConfig
+}
+
+type runtimeConfig struct {
 	enabledFeatures     wasm.Features
 	newEngine           func(wasm.Features) wasm.Engine
 	memoryLimitPages    uint32
@@ -25,15 +139,15 @@ type RuntimeConfig struct {
 }
 
 // engineLessConfig helps avoid copy/pasting the wrong defaults.
-var engineLessConfig = &RuntimeConfig{
+var engineLessConfig = &runtimeConfig{
 	enabledFeatures:     wasm.Features20191205,
 	memoryLimitPages:    wasm.MemoryLimitPages,
 	memoryCapacityPages: func(minPages uint32, maxPages *uint32) uint32 { return minPages },
 }
 
 // clone ensures all fields are copied even if nil.
-func (c *RuntimeConfig) clone() *RuntimeConfig {
-	return &RuntimeConfig{
+func (c *runtimeConfig) clone() *runtimeConfig {
+	return &runtimeConfig{
 		enabledFeatures:     c.enabledFeatures,
 		newEngine:           c.newEngine,
 		memoryLimitPages:    c.memoryLimitPages,
@@ -45,145 +159,82 @@ func (c *RuntimeConfig) clone() *RuntimeConfig {
 //
 // Note: This panics at runtime the runtime.GOOS or runtime.GOARCH does not support JIT. Use NewRuntimeConfig to safely
 // detect and fallback to NewRuntimeConfigInterpreter if needed.
-func NewRuntimeConfigJIT() *RuntimeConfig {
+func NewRuntimeConfigJIT() RuntimeConfig {
 	ret := engineLessConfig.clone()
 	ret.newEngine = jit.NewEngine
 	return ret
 }
 
 // NewRuntimeConfigInterpreter interprets WebAssembly modules instead of compiling them into assembly.
-func NewRuntimeConfigInterpreter() *RuntimeConfig {
+func NewRuntimeConfigInterpreter() RuntimeConfig {
 	ret := engineLessConfig.clone()
 	ret.newEngine = interpreter.NewEngine
 	return ret
 }
 
-// WithMemoryLimitPages limits the maximum number of pages a module can define from 65536 pages (4GiB) to a lower value.
-//
-// Notes:
-// * If a module defines no memory max value, Runtime.CompileModule sets max to the limit.
-// * If a module defines a memory max larger than this limit, it will fail to compile (Runtime.CompileModule).
-// * Any "memory.grow" instruction that results in a larger value than this results in an error at runtime.
-// * Zero is a valid value and results in a crash if any module uses memory.
-//
-// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#grow-mem
-// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-types%E2%91%A0
-func (c *RuntimeConfig) WithMemoryLimitPages(memoryLimitPages uint32) *RuntimeConfig {
+// WithFeatureBulkMemoryOperations implements RuntimeConfig.WithFeatureBulkMemoryOperations
+func (c *runtimeConfig) WithFeatureBulkMemoryOperations(enabled bool) RuntimeConfig {
 	ret := c.clone()
-	ret.memoryLimitPages = memoryLimitPages
+	ret.enabledFeatures = ret.enabledFeatures.Set(wasm.FeatureBulkMemoryOperations, enabled)
 	return ret
 }
 
-// WithMemoryCapacityPages is a function that determines memory capacity in pages (65536 bytes per page). The inputs are
-// the min and possibly nil max defined by the module, and the default is to return the min.
-//
-// Ex. To set capacity to max when exists:
-//	c = c.WithMemoryCapacityPages(func(minPages uint32, maxPages *uint32) uint32 {
-//		if maxPages != nil {
-//			return *maxPages
-//		}
-//		return minPages
-//	})
-//
-// This function is used at compile time (ModuleBuilder.Build or Runtime.CompileModule). Compile will err if the
-// function returns a value lower than minPages or greater than WithMemoryLimitPages.
-func (c *RuntimeConfig) WithMemoryCapacityPages(maxCapacityPages func(minPages uint32, maxPages *uint32) uint32) *RuntimeConfig {
-	if maxCapacityPages == nil {
-		return c // Instead of erring.
-	}
+// WithFeatureMultiValue implements RuntimeConfig.WithFeatureMultiValue
+func (c *runtimeConfig) WithFeatureMultiValue(enabled bool) RuntimeConfig {
 	ret := c.clone()
-	ret.memoryCapacityPages = maxCapacityPages
+	ret.enabledFeatures = ret.enabledFeatures.Set(wasm.FeatureMultiValue, enabled)
 	return ret
 }
 
-// WithFinishedFeatures enables currently supported "finished" feature proposals. Use this to improve compatibility with
-// tools that enable all features by default.
-//
-// Note: The features implied can vary and can lead to unpredictable behavior during updates.
-// Note: This only includes "finished" features, but "finished" is not an official W3C term: it is possible that
-// "finished" features do not make the next W3C recommended WebAssembly core specification.
-// See https://github.com/WebAssembly/spec/tree/main/proposals
-func (c *RuntimeConfig) WithFinishedFeatures() *RuntimeConfig {
+// WithFeatureMutableGlobal implements RuntimeConfig.WithFeatureMutableGlobal
+func (c *runtimeConfig) WithFeatureMutableGlobal(enabled bool) RuntimeConfig {
 	ret := c.clone()
-	ret.enabledFeatures = wasm.FeaturesFinished
+	ret.enabledFeatures = ret.enabledFeatures.Set(wasm.FeatureMutableGlobal, enabled)
 	return ret
 }
 
-// WithFeatureBulkMemoryOperations adds instructions modify ranges of memory or table entries
-// ("bulk-memory-operations"). This defaults to false as the feature was not finished in WebAssembly 1.0 (20191205).
-//
-// Here are the notable effects:
-// * Adds `memory.fill`, `memory.init`, `memory.copy` and `data.drop` instructions.
-// * Adds `table.fill`, `table.init`, `table.copy` and `elem.drop` instructions.
-// * Introduces a "passive" form of element and data segments.
-// * Stops checking "active" element and data segment boundaries at compile-time, meaning they can error at runtime.
-//
-// Note: "bulk-memory-operations" is mixed with the "reference-types" proposal
-// due to the WebAssembly Working Group merging them "mutually dependent".
-// See https://github.com/WebAssembly/spec/blob/main/proposals/bulk-memory-operations/Overview.md
-// See https://github.com/WebAssembly/spec/blob/main/proposals/reference-types/Overview.md
-// See https://github.com/WebAssembly/spec/pull/1287
-func (c *RuntimeConfig) WithFeatureBulkMemoryOperations(enabled bool) *RuntimeConfig {
+// WithFeatureNonTrappingFloatToIntConversion implements RuntimeConfig.WithFeatureNonTrappingFloatToIntConversion
+func (c *runtimeConfig) WithFeatureNonTrappingFloatToIntConversion(enabled bool) RuntimeConfig {
 	ret := c.clone()
-	ret.enabledFeatures = ret.enabledFeatures.Set(wasm.FeatureBulkMemoryOperations, enabled)
+	ret.enabledFeatures = ret.enabledFeatures.Set(wasm.FeatureNonTrappingFloatToIntConversion, enabled)
 	return ret
 }
 
-// WithFeatureMultiValue enables multiple values ("multi-value"). This defaults to false as the feature was not finished
-// in WebAssembly 1.0 (20191205).
-//
-// Here are the notable effects:
-// * Function (`func`) types allow more than one result
-// * Block types (`block`, `loop` and `if`) can be arbitrary function types
-//
-// See https://github.com/WebAssembly/spec/blob/main/proposals/multi-value/Overview.md
-func (c *RuntimeConfig) WithFeatureMultiValue(enabled bool) *RuntimeConfig {
+// WithFeatureSignExtensionOps implements RuntimeConfig.WithFeatureSignExtensionOps
+func (c *runtimeConfig) WithFeatureSignExtensionOps(enabled bool) RuntimeConfig {
 	ret := c.clone()
-	ret.enabledFeatures = ret.enabledFeatures.Set(wasm.FeatureMultiValue, enabled)
+	ret.enabledFeatures = ret.enabledFeatures.Set(wasm.FeatureSignExtensionOps, enabled)
 	return ret
 }
 
-// WithFeatureMutableGlobal allows globals to be mutable. This defaults to true as the feature was finished in
-// WebAssembly 1.0 (20191205).
-//
-// When false, an api.Global can never be cast to an api.MutableGlobal, and any source that includes global vars
-// will fail to parse.
-func (c *RuntimeConfig) WithFeatureMutableGlobal(enabled bool) *RuntimeConfig {
+// WithMemoryCapacityPages implements RuntimeConfig.WithMemoryCapacityPages
+func (c *runtimeConfig) WithMemoryCapacityPages(maxCapacityPages func(minPages uint32, maxPages *uint32) uint32) RuntimeConfig {
+	if maxCapacityPages == nil {
+		return c // Instead of erring.
+	}
 	ret := c.clone()
-	ret.enabledFeatures = ret.enabledFeatures.Set(wasm.FeatureMutableGlobal, enabled)
+	ret.memoryCapacityPages = maxCapacityPages
 	return ret
 }
 
-// WithFeatureNonTrappingFloatToIntConversion enables non-trapping float-to-int conversions.
-// ("nontrapping-float-to-int-conversion"). This defaults to false as the feature was not in WebAssembly 1.0 (20191205).
-//
-// The only effect of enabling this is allowing the following instructions, which return 0 on NaN instead of panicking.
-// * `i32.trunc_sat_f32_s`
-// * `i32.trunc_sat_f32_u`
-// * `i32.trunc_sat_f64_s`
-// * `i32.trunc_sat_f64_u`
-// * `i64.trunc_sat_f32_s`
-// * `i64.trunc_sat_f32_u`
-// * `i64.trunc_sat_f64_s`
-// * `i64.trunc_sat_f64_u`
-//
-// See https://github.com/WebAssembly/spec/blob/main/proposals/nontrapping-float-to-int-conversion/Overview.md
-func (c *RuntimeConfig) WithFeatureNonTrappingFloatToIntConversion(enabled bool) *RuntimeConfig {
+// WithMemoryLimitPages implements RuntimeConfig.WithMemoryLimitPages
+func (c *runtimeConfig) WithMemoryLimitPages(memoryLimitPages uint32) RuntimeConfig {
 	ret := c.clone()
-	ret.enabledFeatures = ret.enabledFeatures.Set(wasm.FeatureNonTrappingFloatToIntConversion, enabled)
+	ret.memoryLimitPages = memoryLimitPages
 	return ret
 }
 
-// WithFeatureSignExtensionOps enables sign extension instructions ("sign-extension-ops"). This defaults to false as the
-// feature was not in WebAssembly 1.0 (20191205).
-//
-// Here are the notable effects:
-// * Adds instructions `i32.extend8_s`, `i32.extend16_s`, `i64.extend8_s`, `i64.extend16_s` and `i64.extend32_s`
-//
-// See https://github.com/WebAssembly/spec/blob/main/proposals/sign-extension-ops/Overview.md
-func (c *RuntimeConfig) WithFeatureSignExtensionOps(enabled bool) *RuntimeConfig {
+// WithWasmCore1 implements RuntimeConfig.WithWasmCore1
+func (c *runtimeConfig) WithWasmCore1() RuntimeConfig {
 	ret := c.clone()
-	ret.enabledFeatures = ret.enabledFeatures.Set(wasm.FeatureSignExtensionOps, enabled)
+	ret.enabledFeatures = wasm.Features20191205
+	return ret
+}
+
+// WithWasmCore2 implements RuntimeConfig.WithWasmCore2
+func (c *runtimeConfig) WithWasmCore2() RuntimeConfig {
+	ret := c.clone()
+	ret.enabledFeatures = wasm.Features20220419
 	return ret
 }
 
diff --git a/config_supported.go b/config_supported.go
index 13ae5d2b..9ee8a141 100644
--- a/config_supported.go
+++ b/config_supported.go
@@ -5,6 +5,6 @@ package wazero
 const JITSupported = true
 
 // NewRuntimeConfig returns NewRuntimeConfigJIT
-func NewRuntimeConfig() *RuntimeConfig {
+func NewRuntimeConfig() RuntimeConfig {
 	return NewRuntimeConfigJIT()
 }
diff --git a/config_unsupported.go b/config_unsupported.go
index c247b7d2..3a1ea520 100644
--- a/config_unsupported.go
+++ b/config_unsupported.go
@@ -5,6 +5,6 @@ package wazero
 const JITSupported = false
 
 // NewRuntimeConfig returns NewRuntimeConfigInterpreter
-func NewRuntimeConfig() *RuntimeConfig {
+func NewRuntimeConfig() RuntimeConfig {
 	return NewRuntimeConfigInterpreter()
 }
diff --git a/examples/multiple-results/multiple-results.go b/examples/multiple-results/multiple-results.go
index 30302c33..c9d583cd 100644
--- a/examples/multiple-results/multiple-results.go
+++ b/examples/multiple-results/multiple-results.go
@@ -48,7 +48,7 @@ func main() {
 	// wazero enables only W3C recommended features by default. Opt-in to other features like so:
 	runtimeWithMultiValue := wazero.NewRuntimeWithConfig(
 		wazero.NewRuntimeConfig().WithFeatureMultiValue(true),
-		// ^^ Note: You can enable all features via WithFinishedFeatures.
+		// ^^ Note: WebAssembly 2.0 (WithWasmCore2) includes "multi-value".
 	)
 
 	// Add a module that uses multiple results values, with functions defined in WebAssembly.
diff --git a/internal/wasm/features.go b/internal/wasm/features.go
index 7c978490..0c6abe1f 100644
--- a/internal/wasm/features.go
+++ b/internal/wasm/features.go
@@ -12,14 +12,20 @@ type Features uint64
 
 // Features20191205 include those finished in WebAssembly 1.0 (20191205).
 //
-// See https://github.com/WebAssembly/proposals/blob/main/finished-proposals.md
 // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205
 const Features20191205 = FeatureMutableGlobal
 
-// FeaturesFinished include all supported finished features, regardless of W3C status.
+// Features20220419 include those finished in WebAssembly 2.0 (20220419).
 //
-// See https://github.com/WebAssembly/proposals/blob/main/finished-proposals.md
-const FeaturesFinished = 0xffffffffffffffff
+// TODO: not yet complete https://github.com/tetratelabs/wazero/issues/484
+// See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/appendix/changes.html#release-1-1
+const Features20220419 = Features20191205 |
+	FeatureBulkMemoryOperations |
+	FeatureMultiValue |
+	FeatureNonTrappingFloatToIntConversion |
+	// TODO: FeatureReferenceTypes |
+	FeatureSignExtensionOps
+	// TODO: FeatureSIMD
 
 const (
 	// FeatureBulkMemoryOperations decides if parsing should succeed on the following instructions:
diff --git a/wasm.go b/wasm.go
index 96d0e644..7296d012 100644
--- a/wasm.go
+++ b/wasm.go
@@ -122,7 +122,11 @@ func NewRuntime() Runtime {
 }
 
 // NewRuntimeWithConfig returns a runtime with the given configuration.
-func NewRuntimeWithConfig(config *RuntimeConfig) Runtime {
+func NewRuntimeWithConfig(rConfig RuntimeConfig) Runtime {
+	config, ok := rConfig.(*runtimeConfig)
+	if !ok {
+		panic(fmt.Errorf("unsupported wazero.RuntimeConfig implementation: %#v", rConfig))
+	}
 	return &runtime{
 		store:               wasm.NewStore(config.enabledFeatures, config.newEngine(config.enabledFeatures)),
 		enabledFeatures:     config.enabledFeatures,


Test patch:
diff --git a/builder_test.go b/builder_test.go
index f7a7dfd6..bb250f44 100644
--- a/builder_test.go
+++ b/builder_test.go
@@ -362,12 +362,12 @@ func TestNewModuleBuilder_Build(t *testing.T) {
 func TestNewModuleBuilder_Build_Errors(t *testing.T) {
 	tests := []struct {
 		name        string
-		input       func(*RuntimeConfig) ModuleBuilder
+		input       func(RuntimeConfig) ModuleBuilder
 		expectedErr string
 	}{
 		{
 			name: "memory min > limit", // only one test to avoid duplicating tests in module_test.go
-			input: func(cfg *RuntimeConfig) ModuleBuilder {
+			input: func(cfg RuntimeConfig) ModuleBuilder {
 				return NewRuntimeWithConfig(cfg).NewModuleBuilder("").
 					ExportMemory("memory", math.MaxUint32)
 			},
@@ -375,7 +375,7 @@ func TestNewModuleBuilder_Build_Errors(t *testing.T) {
 		},
 		{
 			name: "memory cap < min", // only one test to avoid duplicating tests in module_test.go
-			input: func(cfg *RuntimeConfig) ModuleBuilder {
+			input: func(cfg RuntimeConfig) ModuleBuilder {
 				cfg = cfg.WithMemoryCapacityPages(func(minPages uint32, maxPages *uint32) uint32 {
 					return 1
 				})
diff --git a/config_test.go b/config_test.go
index 0c871a13..dcc77cf5 100644
--- a/config_test.go
+++ b/config_test.go
@@ -14,90 +14,108 @@ import (
 func TestRuntimeConfig(t *testing.T) {
 	tests := []struct {
 		name     string
-		with     func(*RuntimeConfig) *RuntimeConfig
-		expected *RuntimeConfig
+		with     func(RuntimeConfig) RuntimeConfig
+		expected RuntimeConfig
 	}{
 		{
 			name: "WithMemoryLimitPages",
-			with: func(c *RuntimeConfig) *RuntimeConfig {
+			with: func(c RuntimeConfig) RuntimeConfig {
 				return c.WithMemoryLimitPages(1)
 			},
-			expected: &RuntimeConfig{
+			expected: &runtimeConfig{
 				memoryLimitPages: 1,
 			},
 		},
 		{
 			name: "bulk-memory-operations",
-			with: func(c *RuntimeConfig) *RuntimeConfig {
+			with: func(c RuntimeConfig) RuntimeConfig {
 				return c.WithFeatureBulkMemoryOperations(true)
 			},
-			expected: &RuntimeConfig{
+			expected: &runtimeConfig{
 				enabledFeatures: wasm.FeatureBulkMemoryOperations,
 			},
 		},
 		{
 			name: "multi-value",
-			with: func(c *RuntimeConfig) *RuntimeConfig {
+			with: func(c RuntimeConfig) RuntimeConfig {
 				return c.WithFeatureMultiValue(true)
 			},
-			expected: &RuntimeConfig{
+			expected: &runtimeConfig{
 				enabledFeatures: wasm.FeatureMultiValue,
 			},
 		},
 		{
 			name: "mutable-global",
-			with: func(c *RuntimeConfig) *RuntimeConfig {
+			with: func(c RuntimeConfig) RuntimeConfig {
 				return c.WithFeatureMutableGlobal(true)
 			},
-			expected: &RuntimeConfig{
+			expected: &runtimeConfig{
 				enabledFeatures: wasm.FeatureMutableGlobal,
 			},
 		},
 		{
 			name: "nontrapping-float-to-int-conversion",
-			with: func(c *RuntimeConfig) *RuntimeConfig {
+			with: func(c RuntimeConfig) RuntimeConfig {
 				return c.WithFeatureNonTrappingFloatToIntConversion(true)
 			},
-			expected: &RuntimeConfig{
+			expected: &runtimeConfig{
 				enabledFeatures: wasm.FeatureNonTrappingFloatToIntConversion,
 			},
 		},
 		{
 			name: "sign-extension-ops",
-			with: func(c *RuntimeConfig) *RuntimeConfig {
+			with: func(c RuntimeConfig) RuntimeConfig {
 				return c.WithFeatureSignExtensionOps(true)
 			},
-			expected: &RuntimeConfig{
+			expected: &runtimeConfig{
 				enabledFeatures: wasm.FeatureSignExtensionOps,
 			},
 		},
+		{
+			name: "REC-wasm-core-1-20191205",
+			with: func(c RuntimeConfig) RuntimeConfig {
+				return c.WithFeatureSignExtensionOps(true).WithWasmCore1()
+			},
+			expected: &runtimeConfig{
+				enabledFeatures: wasm.Features20191205,
+			},
+		},
+		{
+			name: "WD-wasm-core-2-20220419",
+			with: func(c RuntimeConfig) RuntimeConfig {
+				return c.WithFeatureMutableGlobal(false).WithWasmCore2()
+			},
+			expected: &runtimeConfig{
+				enabledFeatures: wasm.Features20220419,
+			},
+		},
 	}
 	for _, tt := range tests {
 		tc := tt
 
 		t.Run(tc.name, func(t *testing.T) {
-			input := &RuntimeConfig{}
+			input := &runtimeConfig{}
 			rc := tc.with(input)
 			require.Equal(t, tc.expected, rc)
 			// The source wasn't modified
-			require.Equal(t, &RuntimeConfig{}, input)
+			require.Equal(t, &runtimeConfig{}, input)
 		})
 	}
 
 	t.Run("WithMemoryCapacityPages", func(t *testing.T) {
-		c := NewRuntimeConfig()
+		c := NewRuntimeConfig().(*runtimeConfig)
 
 		// Test default returns min
 		require.Equal(t, uint32(1), c.memoryCapacityPages(1, nil))
 
 		// Nil ignored
-		c = c.WithMemoryCapacityPages(nil)
+		c = c.WithMemoryCapacityPages(nil).(*runtimeConfig)
 		require.Equal(t, uint32(1), c.memoryCapacityPages(1, nil))
 
 		// Assign a valid function
 		c = c.WithMemoryCapacityPages(func(minPages uint32, maxPages *uint32) uint32 {
 			return 2
-		})
+		}).(*runtimeConfig)
 		// Returns updated value
 		require.Equal(t, uint32(2), c.memoryCapacityPages(1, nil))
 	})
@@ -108,13 +126,13 @@ func TestRuntimeConfig_FeatureToggle(t *testing.T) {
 		name          string
 		feature       wasm.Features
 		expectDefault bool
-		setFeature    func(*RuntimeConfig, bool) *RuntimeConfig
+		setFeature    func(RuntimeConfig, bool) RuntimeConfig
 	}{
 		{
 			name:          "bulk-memory-operations",
 			feature:       wasm.FeatureBulkMemoryOperations,
 			expectDefault: false,
-			setFeature: func(c *RuntimeConfig, v bool) *RuntimeConfig {
+			setFeature: func(c RuntimeConfig, v bool) RuntimeConfig {
 				return c.WithFeatureBulkMemoryOperations(v)
 			},
 		},
@@ -122,7 +140,7 @@ func TestRuntimeConfig_FeatureToggle(t *testing.T) {
 			name:          "multi-value",
 			feature:       wasm.FeatureMultiValue,
 			expectDefault: false,
-			setFeature: func(c *RuntimeConfig, v bool) *RuntimeConfig {
+			setFeature: func(c RuntimeConfig, v bool) RuntimeConfig {
 				return c.WithFeatureMultiValue(v)
 			},
 		},
@@ -130,7 +148,7 @@ func TestRuntimeConfig_FeatureToggle(t *testing.T) {
 			name:          "mutable-global",
 			feature:       wasm.FeatureMutableGlobal,
 			expectDefault: true,
-			setFeature: func(c *RuntimeConfig, v bool) *RuntimeConfig {
+			setFeature: func(c RuntimeConfig, v bool) RuntimeConfig {
 				return c.WithFeatureMutableGlobal(v)
 			},
 		},
@@ -138,7 +156,7 @@ func TestRuntimeConfig_FeatureToggle(t *testing.T) {
 			name:          "nontrapping-float-to-int-conversion",
 			feature:       wasm.FeatureNonTrappingFloatToIntConversion,
 			expectDefault: false,
-			setFeature: func(c *RuntimeConfig, v bool) *RuntimeConfig {
+			setFeature: func(c RuntimeConfig, v bool) RuntimeConfig {
 				return c.WithFeatureNonTrappingFloatToIntConversion(v)
 			},
 		},
@@ -146,7 +164,7 @@ func TestRuntimeConfig_FeatureToggle(t *testing.T) {
 			name:          "sign-extension-ops",
 			feature:       wasm.FeatureSignExtensionOps,
 			expectDefault: false,
-			setFeature: func(c *RuntimeConfig, v bool) *RuntimeConfig {
+			setFeature: func(c RuntimeConfig, v bool) RuntimeConfig {
 				return c.WithFeatureSignExtensionOps(v)
 			},
 		},
@@ -156,19 +174,19 @@ func TestRuntimeConfig_FeatureToggle(t *testing.T) {
 		tc := tt
 
 		t.Run(tc.name, func(t *testing.T) {
-			c := NewRuntimeConfig()
+			c := NewRuntimeConfig().(*runtimeConfig)
 			require.Equal(t, tc.expectDefault, c.enabledFeatures.Get(tc.feature))
 
 			// Set to false even if it was initially false.
-			c = tc.setFeature(c, false)
+			c = tc.setFeature(c, false).(*runtimeConfig)
 			require.False(t, c.enabledFeatures.Get(tc.feature))
 
 			// Set true makes it true
-			c = tc.setFeature(c, true)
+			c = tc.setFeature(c, true).(*runtimeConfig)
 			require.True(t, c.enabledFeatures.Get(tc.feature))
 
 			// Set false makes it false again
-			c = tc.setFeature(c, false)
+			c = tc.setFeature(c, false).(*runtimeConfig)
 			require.False(t, c.enabledFeatures.Get(tc.feature))
 		})
 	}
diff --git a/internal/integration_test/bench/bench_test.go b/internal/integration_test/bench/bench_test.go
index 218e03ae..b381f3db 100644
--- a/internal/integration_test/bench/bench_test.go
+++ b/internal/integration_test/bench/bench_test.go
@@ -153,8 +153,8 @@ func runRandomMatMul(b *testing.B, m api.Module) {
 	}
 }
 
-func instantiateHostFunctionModuleWithEngine(b *testing.B, engine *wazero.RuntimeConfig) api.Module {
-	r := createRuntime(b, engine)
+func instantiateHostFunctionModuleWithEngine(b *testing.B, config wazero.RuntimeConfig) api.Module {
+	r := createRuntime(b, config)
 
 	// InstantiateModuleFromCode runs the "_start" function which is what TinyGo compiles "main" to.
 	m, err := r.InstantiateModuleFromCode(testCtx, caseWasm)
@@ -164,7 +164,7 @@ func instantiateHostFunctionModuleWithEngine(b *testing.B, engine *wazero.Runtim
 	return m
 }
 
-func createRuntime(b *testing.B, engine *wazero.RuntimeConfig) wazero.Runtime {
+func createRuntime(b *testing.B, config wazero.RuntimeConfig) wazero.Runtime {
 	getRandomString := func(ctx context.Context, m api.Module, retBufPtr uint32, retBufSize uint32) {
 		results, err := m.ExportedFunction("allocate_buffer").Call(ctx, 10)
 		if err != nil {
@@ -179,7 +179,7 @@ func createRuntime(b *testing.B, engine *wazero.RuntimeConfig) wazero.Runtime {
 		m.Memory().Write(ctx, offset, b)
 	}
 
-	r := wazero.NewRuntimeWithConfig(engine)
+	r := wazero.NewRuntimeWithConfig(config)
 
 	_, err := r.NewModuleBuilder("env").
 		ExportFunction("get_random_string", getRandomString).
diff --git a/internal/integration_test/engine/adhoc_test.go b/internal/integration_test/engine/adhoc_test.go
index 76310b2d..b7e5f16c 100644
--- a/internal/integration_test/engine/adhoc_test.go
+++ b/internal/integration_test/engine/adhoc_test.go
@@ -42,7 +42,7 @@ func TestEngineInterpreter(t *testing.T) {
 	runAllTests(t, tests, wazero.NewRuntimeConfigInterpreter())
 }
 
-func runAllTests(t *testing.T, tests map[string]func(t *testing.T, r wazero.Runtime), config *wazero.RuntimeConfig) {
+func runAllTests(t *testing.T, tests map[string]func(t *testing.T, r wazero.Runtime), config wazero.RuntimeConfig) {
 	for name, testf := range tests {
 		name := name   // pin
 		testf := testf // pin
diff --git a/internal/integration_test/post1_0/bulk-memory-operations/spec_test.go b/internal/integration_test/post1_0/bulk-memory-operations/spec_test.go
index 0619f891..23c6d2a2 100644
--- a/internal/integration_test/post1_0/bulk-memory-operations/spec_test.go
+++ b/internal/integration_test/post1_0/bulk-memory-operations/spec_test.go
@@ -45,7 +45,7 @@ var (
 	elemDropWasm []byte
 )
 
-func requireErrorOnBulkMemoryFeatureDisabled(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig, bin []byte) {
+func requireErrorOnBulkMemoryFeatureDisabled(t *testing.T, newRuntimeConfig func() wazero.RuntimeConfig, bin []byte) {
 	t.Run("disabled", func(t *testing.T) {
 		// bulk-memory-operations is disabled by default.
 		r := wazero.NewRuntimeWithConfig(newRuntimeConfig())
@@ -54,7 +54,7 @@ func requireErrorOnBulkMemoryFeatureDisabled(t *testing.T, newRuntimeConfig func
 	})
 }
 
-func testTableCopy(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig) {
+func testTableCopy(t *testing.T, newRuntimeConfig func() wazero.RuntimeConfig) {
 	t.Run("table.copy", func(t *testing.T) {
 
 		requireErrorOnBulkMemoryFeatureDisabled(t, newRuntimeConfig, tableCopyWasm)
@@ -123,7 +123,7 @@ func testTableCopy(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig)
 	})
 }
 
-func testTableInit(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig) {
+func testTableInit(t *testing.T, newRuntimeConfig func() wazero.RuntimeConfig) {
 	t.Run("table.init", func(t *testing.T) {
 		requireErrorOnBulkMemoryFeatureDisabled(t, newRuntimeConfig, tableInitWasm)
 
@@ -166,7 +166,7 @@ func testTableInit(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig)
 	})
 }
 
-func testElemDrop(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig) {
+func testElemDrop(t *testing.T, newRuntimeConfig func() wazero.RuntimeConfig) {
 	t.Run("elem.drop", func(t *testing.T) {
 		requireErrorOnBulkMemoryFeatureDisabled(t, newRuntimeConfig, elemDropWasm)
 
@@ -194,7 +194,7 @@ func testElemDrop(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig) {
 	})
 }
 
-func testBulkMemoryOperations(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig) {
+func testBulkMemoryOperations(t *testing.T, newRuntimeConfig func() wazero.RuntimeConfig) {
 	requireErrorOnBulkMemoryFeatureDisabled(t, newRuntimeConfig, bulkMemoryOperationsWasm)
 
 	t.Run("enabled", func(t *testing.T) {
diff --git a/internal/integration_test/post1_0/multi-value/spec_test.go b/internal/integration_test/post1_0/multi-value/spec_test.go
index 795400c2..b74043f3 100644
--- a/internal/integration_test/post1_0/multi-value/spec_test.go
+++ b/internal/integration_test/post1_0/multi-value/spec_test.go
@@ -28,7 +28,7 @@ func TestMultiValue_Interpreter(t *testing.T) {
 //go:embed testdata/multi_value.wasm
 var multiValueWasm []byte
 
-func testMultiValue(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig) {
+func testMultiValue(t *testing.T, newRuntimeConfig func() wazero.RuntimeConfig) {
 	t.Run("disabled", func(t *testing.T) {
 		// multi-value is disabled by default.
 		r := wazero.NewRuntimeWithConfig(newRuntimeConfig())
diff --git a/internal/integration_test/post1_0/nontrapping-float-to-int-conversion/spec_test.go b/internal/integration_test/post1_0/nontrapping-float-to-int-conversion/spec_test.go
index 44a43d56..0b318ecd 100644
--- a/internal/integration_test/post1_0/nontrapping-float-to-int-conversion/spec_test.go
+++ b/internal/integration_test/post1_0/nontrapping-float-to-int-conversion/spec_test.go
@@ -56,7 +56,7 @@ var nonTrappingFloatToIntConversion = []byte(`(module $conversions.wast
 )
 `)
 
-func testNonTrappingFloatToIntConversion(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig) {
+func testNonTrappingFloatToIntConversion(t *testing.T, newRuntimeConfig func() wazero.RuntimeConfig) {
 	t.Run("disabled", func(t *testing.T) {
 		// Non-trapping Float-to-int Conversions are disabled by default.
 		r := wazero.NewRuntimeWithConfig(newRuntimeConfig())
diff --git a/internal/integration_test/post1_0/sign-extension-ops/spec_test.go b/internal/integration_test/post1_0/sign-extension-ops/spec_test.go
index 598615a0..434ef3a2 100644
--- a/internal/integration_test/post1_0/sign-extension-ops/spec_test.go
+++ b/internal/integration_test/post1_0/sign-extension-ops/spec_test.go
@@ -45,7 +45,7 @@ var signExtend = []byte(`(module
 )
 `)
 
-func testSignExtensionOps(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig) {
+func testSignExtensionOps(t *testing.T, newRuntimeConfig func() wazero.RuntimeConfig) {
 	t.Run("disabled", func(t *testing.T) {
 		// Sign-extension is disabled by default.
 		r := wazero.NewRuntimeWithConfig(newRuntimeConfig())
diff --git a/internal/integration_test/vs/codec.go b/internal/integration_test/vs/codec.go
index 12e79140..629ce324 100644
--- a/internal/integration_test/vs/codec.go
+++ b/internal/integration_test/vs/codec.go
@@ -92,7 +92,7 @@ func newExample() *wasm.Module {
 func BenchmarkWat2Wasm(b *testing.B, vsName string, vsWat2Wasm func([]byte) error) {
 	b.Run("wazero", func(b *testing.B) {
 		for i := 0; i < b.N; i++ {
-			if m, err := text.DecodeModule(exampleText, wasm.FeaturesFinished, wasm.MemoryLimitPages); err != nil {
+			if m, err := text.DecodeModule(exampleText, wasm.Features20220419, wasm.MemoryLimitPages); err != nil {
 				b.Fatal(err)
 			} else {
 				_ = binary.EncodeModule(m)
diff --git a/internal/integration_test/vs/codec_test.go b/internal/integration_test/vs/codec_test.go
index 585edabb..d77c4489 100644
--- a/internal/integration_test/vs/codec_test.go
+++ b/internal/integration_test/vs/codec_test.go
@@ -14,19 +14,19 @@ import (
 
 func TestExampleUpToDate(t *testing.T) {
 	t.Run("binary.DecodeModule", func(t *testing.T) {
-		m, err := binary.DecodeModule(exampleBinary, wasm.FeaturesFinished, wasm.MemoryLimitPages)
+		m, err := binary.DecodeModule(exampleBinary, wasm.Features20220419, wasm.MemoryLimitPages)
 		require.NoError(t, err)
 		require.Equal(t, example, m)
 	})
 
 	t.Run("text.DecodeModule", func(t *testing.T) {
-		m, err := text.DecodeModule(exampleText, wasm.FeaturesFinished, wasm.MemoryLimitPages)
+		m, err := text.DecodeModule(exampleText, wasm.Features20220419, wasm.MemoryLimitPages)
 		require.NoError(t, err)
 		require.Equal(t, example, m)
 	})
 
 	t.Run("Executable", func(t *testing.T) {
-		r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfig().WithFinishedFeatures())
+		r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfig().WithWasmCore2())
 
 		// Add WASI to satisfy import tests
 		wm, err := wasi.InstantiateSnapshotPreview1(testCtx, r)
@@ -49,7 +49,7 @@ func BenchmarkCodec(b *testing.B) {
 	b.Run("binary.DecodeModule", func(b *testing.B) {
 		b.ReportAllocs()
 		for i := 0; i < b.N; i++ {
-			if _, err := binary.DecodeModule(exampleBinary, wasm.FeaturesFinished, wasm.MemoryLimitPages); err != nil {
+			if _, err := binary.DecodeModule(exampleBinary, wasm.Features20220419, wasm.MemoryLimitPages); err != nil {
 				b.Fatal(err)
 			}
 		}
@@ -63,7 +63,7 @@ func BenchmarkCodec(b *testing.B) {
 	b.Run("text.DecodeModule", func(b *testing.B) {
 		b.ReportAllocs()
 		for i := 0; i < b.N; i++ {
-			if _, err := text.DecodeModule(exampleText, wasm.FeaturesFinished, wasm.MemoryLimitPages); err != nil {
+			if _, err := text.DecodeModule(exampleText, wasm.Features20220419, wasm.MemoryLimitPages); err != nil {
 				b.Fatal(err)
 			}
 		}
diff --git a/internal/integration_test/vs/runtime.go b/internal/integration_test/vs/runtime.go
index 197dca61..e70d3f91 100644
--- a/internal/integration_test/vs/runtime.go
+++ b/internal/integration_test/vs/runtime.go
@@ -38,20 +38,20 @@ type Module interface {
 }
 
 func NewWazeroInterpreterRuntime() Runtime {
-	return newWazeroRuntime("wazero-interpreter", wazero.NewRuntimeConfigInterpreter().WithFinishedFeatures())
+	return newWazeroRuntime("wazero-interpreter", wazero.NewRuntimeConfigInterpreter().WithWasmCore2())
 }
 
 func NewWazeroJITRuntime() Runtime {
-	return newWazeroRuntime(jitRuntime, wazero.NewRuntimeConfigJIT().WithFinishedFeatures())
+	return newWazeroRuntime(jitRuntime, wazero.NewRuntimeConfigJIT().WithWasmCore2())
 }
 
-func newWazeroRuntime(name string, config *wazero.RuntimeConfig) *wazeroRuntime {
+func newWazeroRuntime(name string, config wazero.RuntimeConfig) *wazeroRuntime {
 	return &wazeroRuntime{name: name, config: config}
 }
 
 type wazeroRuntime struct {
 	name          string
-	config        *wazero.RuntimeConfig
+	config        wazero.RuntimeConfig
 	runtime       wazero.Runtime
 	logFn         func([]byte) error
 	env, compiled *wazero.CompiledCode
diff --git a/internal/modgen/modgen_test.go b/internal/modgen/modgen_test.go
index e5801066..cbadafd9 100644
--- a/internal/modgen/modgen_test.go
+++ b/internal/modgen/modgen_test.go
@@ -28,7 +28,7 @@ const (
 func TestModGen(t *testing.T) {
 	tested := map[string]struct{}{}
 	rand := rand.New(rand.NewSource(0)) // use deterministic seed source for easy debugging.
-	runtime := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfig().WithFinishedFeatures())
+	runtime := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfig().WithWasmCore2())
 	for _, size := range []int{1, 2, 5, 10, 50, 100} {
 		for i := 0; i < 100; i++ {
 			seed := make([]byte, size)
diff --git a/internal/wasm/binary/function_test.go b/internal/wasm/binary/function_test.go
index ac59b9c6..7608ddb4 100644
--- a/internal/wasm/binary/function_test.go
+++ b/internal/wasm/binary/function_test.go
@@ -72,7 +72,7 @@ func TestFunctionType(t *testing.T) {
 		})
 
 		t.Run(fmt.Sprintf("decode - %s", tc.name), func(t *testing.T) {
-			binary, err := decodeFunctionType(wasm.FeaturesFinished, bytes.NewReader(b))
+			binary, err := decodeFunctionType(wasm.Features20220419, bytes.NewReader(b))
 			require.NoError(t, err)
 			require.Equal(t, binary, tc.input)
 		})
diff --git a/internal/wasm/features_test.go b/internal/wasm/features_test.go
index 08e08c7a..6291ce4d 100644
--- a/internal/wasm/features_test.go
+++ b/internal/wasm/features_test.go
@@ -62,7 +62,7 @@ func TestFeatures_String(t *testing.T) {
 		{name: "multi-value", feature: FeatureMultiValue, expected: "multi-value"},
 		{name: "features", feature: FeatureMutableGlobal | FeatureMultiValue, expected: "multi-value|mutable-global"},
 		{name: "undefined", feature: 1 << 63, expected: ""},
-		{name: "all", feature: FeaturesFinished, expected: "bulk-memory-operations|multi-value|mutable-global|nontrapping-float-to-int-conversion|sign-extension-ops"},
+		{name: "2.0", feature: Features20220419, expected: "bulk-memory-operations|multi-value|mutable-global|nontrapping-float-to-int-conversion|sign-extension-ops"},
 	}
 
 	for _, tt := range tests {
diff --git a/internal/wasm/gofunc_test.go b/internal/wasm/gofunc_test.go
index fe629047..d06ee15c 100644
--- a/internal/wasm/gofunc_test.go
+++ b/internal/wasm/gofunc_test.go
@@ -247,7 +247,7 @@ func TestPopGoFuncParams(t *testing.T) {
 
 		t.Run(tc.name, func(t *testing.T) {
 			goFunc := reflect.ValueOf(tc.inputFunc)
-			fk, _, err := getFunctionType(&goFunc, FeaturesFinished)
+			fk, _, err := getFunctionType(&goFunc, Features20220419)
 			require.NoError(t, err)
 
 			vals := PopGoFuncParams(&FunctionInstance{Kind: fk, GoFunc: &goFunc}, (&stack{stackVals}).pop)
@@ -387,7 +387,7 @@ func TestCallGoFunc(t *testing.T) {
 
 		t.Run(tc.name, func(t *testing.T) {
 			goFunc := reflect.ValueOf(tc.inputFunc)
-			fk, _, err := getFunctionType(&goFunc, FeaturesFinished)
+			fk, _, err := getFunctionType(&goFunc, Features20220419)
 			require.NoError(t, err)
 
 			results := CallGoFunc(testCtx, callCtx, &FunctionInstance{Kind: fk, GoFunc: &goFunc}, tc.inputParams)
diff --git a/internal/wasm/text/decoder_test.go b/internal/wasm/text/decoder_test.go
index 969ffb31..de411d0b 100644
--- a/internal/wasm/text/decoder_test.go
+++ b/internal/wasm/text/decoder_test.go
@@ -1564,7 +1564,7 @@ func TestDecodeModule(t *testing.T) {
 		tc := tt
 
 		t.Run(tc.name, func(t *testing.T) {
-			m, err := DecodeModule([]byte(tc.input), wasm.FeaturesFinished, wasm.MemoryLimitPages)
+			m, err := DecodeModule([]byte(tc.input), wasm.Features20220419, wasm.MemoryLimitPages)
 			require.NoError(t, err)
 			require.Equal(t, tc.expected, m)
 		})
diff --git a/internal/wasm/text/func_parser_test.go b/internal/wasm/text/func_parser_test.go
index 56a68e83..3e9adbec 100644
--- a/internal/wasm/text/func_parser_test.go
+++ b/internal/wasm/text/func_parser_test.go
@@ -272,7 +272,7 @@ func TestFuncParser(t *testing.T) {
 			}
 
 			module := &wasm.Module{}
-			fp := newFuncParser(wasm.FeaturesFinished, &typeUseParser{module: module}, newIndexNamespace(module.SectionElementCount), setFunc)
+			fp := newFuncParser(wasm.Features20220419, &typeUseParser{module: module}, newIndexNamespace(module.SectionElementCount), setFunc)
 			require.NoError(t, parseFunc(fp, tc.source))
 			require.Equal(t, tc.expected, parsedCode)
 		})
@@ -390,7 +390,7 @@ func TestFuncParser_Call_Resolved(t *testing.T) {
 				return parseErr, nil
 			}
 
-			fp := newFuncParser(wasm.FeaturesFinished, &typeUseParser{module: &wasm.Module{}}, funcNamespace, setFunc)
+			fp := newFuncParser(wasm.Features20220419, &typeUseParser{module: &wasm.Module{}}, funcNamespace, setFunc)
 			require.NoError(t, parseFunc(fp, tc.source))
 			require.Equal(t, tc.expected, parsedCode)
 		})
diff --git a/internal/wasm/text/type_parser_test.go b/internal/wasm/text/type_parser_test.go
index 0bbfca26..49409351 100644
--- a/internal/wasm/text/type_parser_test.go
+++ b/internal/wasm/text/type_parser_test.go
@@ -189,7 +189,7 @@ func TestTypeParser(t *testing.T) {
 				require.Equal(t, wasm.SectionIDType, sectionID)
 				return 0
 			})
-			parsed, tp, err := parseFunctionType(wasm.FeaturesFinished, typeNamespace, tc.input)
+			parsed, tp, err := parseFunctionType(wasm.Features20220419, typeNamespace, tc.input)
 			require.NoError(t, err)
 			require.Equal(t, tc.expected, parsed)
 			require.Equal(t, uint32(1), tp.typeNamespace.count)
@@ -291,13 +291,13 @@ func TestTypeParser_Errors(t *testing.T) {
 		{
 			name:            "result second wrong",
 			input:           "(type (func (result i32) (result i33)))",
-			enabledFeatures: wasm.FeaturesFinished,
+			enabledFeatures: wasm.Features20220419,
 			expectedErr:     "unknown type: i33",
 		},
 		{
 			name:            "result second redundant type wrong",
 			input:           "(type (func (result i32) (result i32 i33)))",
-			enabledFeatures: wasm.FeaturesFinished,
+			enabledFeatures: wasm.Features20220419,
 			expectedErr:     "unknown type: i33",
 		},
 		{
diff --git a/internal/wasm/text/typeuse_parser_test.go b/internal/wasm/text/typeuse_parser_test.go
index d5eb4756..64ff20e5 100644
--- a/internal/wasm/text/typeuse_parser_test.go
+++ b/internal/wasm/text/typeuse_parser_test.go
@@ -160,7 +160,7 @@ func TestTypeUseParser_InlinesTypesWhenNotYetAdded(t *testing.T) {
 
 	runTypeUseParserTests(t, tests, func(tc *typeUseParserTest) (*typeUseParser, func(t *testing.T)) {
 		module := &wasm.Module{}
-		tp := newTypeUseParser(wasm.FeaturesFinished, module, newIndexNamespace(module.SectionElementCount))
+		tp := newTypeUseParser(wasm.Features20220419, module, newIndexNamespace(module.SectionElementCount))
 		return tp, func(t *testing.T) {
 			// We should have inlined the type, and it is the first type use, which means the inlined index is zero
 			require.Zero(t, tp.inlinedTypeIndices[0].inlinedIdx)
@@ -196,7 +196,7 @@ func TestTypeUseParser_UnresolvedType(t *testing.T) {
 	}
 	runTypeUseParserTests(t, tests, func(tc *typeUseParserTest) (*typeUseParser, func(t *testing.T)) {
 		module := &wasm.Module{}
-		tp := newTypeUseParser(wasm.FeaturesFinished, module, newIndexNamespace(module.SectionElementCount))
+		tp := newTypeUseParser(wasm.Features20220419, module, newIndexNamespace(module.SectionElementCount))
 		return tp, func(t *testing.T) {
 			require.NotNil(t, tp.typeNamespace.unresolvedIndices)
 			if tc.expectedInlinedType == nil {
@@ -306,7 +306,7 @@ func TestTypeUseParser_ReuseExistingType(t *testing.T) {
 		require.NoError(t, err)
 		typeNamespace.count++
 
-		tp := newTypeUseParser(wasm.FeaturesFinished, module, typeNamespace)
+		tp := newTypeUseParser(wasm.Features20220419, module, typeNamespace)
 		return tp, func(t *testing.T) {
 			require.Zero(t, len(tp.typeNamespace.unresolvedIndices))
 			require.Zero(t, len(tp.inlinedTypes))
@@ -340,7 +340,7 @@ func TestTypeUseParser_ReuseExistingInlinedType(t *testing.T) {
 	}
 	runTypeUseParserTests(t, tests, func(tc *typeUseParserTest) (*typeUseParser, func(t *testing.T)) {
 		module := &wasm.Module{}
-		tp := newTypeUseParser(wasm.FeaturesFinished, module, newIndexNamespace(module.SectionElementCount))
+		tp := newTypeUseParser(wasm.Features20220419, module, newIndexNamespace(module.SectionElementCount))
 		// inline a type that doesn't match the test
 		require.NoError(t, parseTypeUse(tp, "((param i32 i64))", ignoreTypeUse))
 		// inline the test type
@@ -386,7 +386,7 @@ func TestTypeUseParser_BeginResets(t *testing.T) {
 	}
 	runTypeUseParserTests(t, tests, func(tc *typeUseParserTest) (*typeUseParser, func(t *testing.T)) {
 		module := &wasm.Module{}
-		tp := newTypeUseParser(wasm.FeaturesFinished, module, newIndexNamespace(module.SectionElementCount))
+		tp := newTypeUseParser(wasm.Features20220419, module, newIndexNamespace(module.SectionElementCount))
 		// inline a type that uses all fields
 		require.NoError(t, parseTypeUse(tp, "((type $i32i64_i32) (param $x i32) (param $y i64) (result i32))", ignoreTypeUse))
 		require.NoError(t, parseTypeUse(tp, tc.input, ignoreTypeUse))
@@ -510,13 +510,13 @@ func TestTypeUseParser_Errors(t *testing.T) {
 		{
 			name:            "result second wrong",
 			input:           "((result i32) (result i33))",
-			enabledFeatures: wasm.FeaturesFinished,
+			enabledFeatures: wasm.Features20220419,
 			expectedErr:     "1:23: unknown type: i33",
 		},
 		{
 			name:            "result second redundant type wrong",
 			input:           "((result i32) (result i32 i33))",
-			enabledFeatures: wasm.FeaturesFinished,
+			enabledFeatures: wasm.Features20220419,
 			expectedErr:     "1:27: unknown type: i33",
 		},
 		{
@@ -600,7 +600,7 @@ func TestTypeUseParser_FailsMatch(t *testing.T) {
 	require.NoError(t, err)
 	typeNamespace.count++
 
-	tp := newTypeUseParser(wasm.FeaturesFinished, module, typeNamespace)
+	tp := newTypeUseParser(wasm.Features20220419, module, typeNamespace)
 	tests := []struct{ name, source, expectedErr string }{
 		{
 			name:        "nullary index",
diff --git a/internal/wazeroir/compiler_test.go b/internal/wazeroir/compiler_test.go
index 61fc243e..948a5d3c 100644
--- a/internal/wazeroir/compiler_test.go
+++ b/internal/wazeroir/compiler_test.go
@@ -85,7 +85,7 @@ func TestCompile(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			enabledFeatures := tc.enabledFeatures
 			if enabledFeatures == 0 {
-				enabledFeatures = wasm.FeaturesFinished
+				enabledFeatures = wasm.Features20220419
 			}
 
 			res, err := CompileFunctions(ctx, enabledFeatures, tc.module)
@@ -487,7 +487,7 @@ func TestCompile_MultiValue(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			enabledFeatures := tc.enabledFeatures
 			if enabledFeatures == 0 {
-				enabledFeatures = wasm.FeaturesFinished
+				enabledFeatures = wasm.Features20220419
 			}
 			res, err := CompileFunctions(ctx, enabledFeatures, tc.module)
 			require.NoError(t, err)
@@ -550,7 +550,7 @@ func TestCompile_SignExtensionOps(t *testing.T) {
 
 func requireCompilationResult(t *testing.T, enabledFeatures wasm.Features, expected *CompilationResult, module *wasm.Module) {
 	if enabledFeatures == 0 {
-		enabledFeatures = wasm.FeaturesFinished
+		enabledFeatures = wasm.Features20220419
 	}
 	res, err := CompileFunctions(ctx, enabledFeatures, module)
 	require.NoError(t, err)
@@ -558,7 +558,7 @@ func requireCompilationResult(t *testing.T, enabledFeatures wasm.Features, expec
 }
 
 func requireModuleText(t *testing.T, source string) *wasm.Module {
-	m, err := text.DecodeModule([]byte(source), wasm.FeaturesFinished, wasm.MemoryLimitPages)
+	m, err := text.DecodeModule([]byte(source), wasm.Features20220419, wasm.MemoryLimitPages)
 	require.NoError(t, err)
 	return m
 }
diff --git a/wasm_test.go b/wasm_test.go
index 9ae7953a..b6a4c49b 100644
--- a/wasm_test.go
+++ b/wasm_test.go
@@ -18,6 +18,16 @@ import (
 // testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors.
 var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")
 
+func TestNewRuntimeWithConfig_PanicsOnWrongImpl(t *testing.T) {
+	// It is too burdensome to define an impl of RuntimeConfig in tests just to verify the error when it is wrong.
+	// Instead, we pass nil which is implicitly the wrong type, as that's less work!
+	err := require.CapturePanic(func() {
+		NewRuntimeWithConfig(nil)
+	})
+
+	require.EqualError(t, err, "unsupported wazero.RuntimeConfig implementation: <nil>")
+}
+
 func TestRuntime_CompileModule(t *testing.T) {
 	tests := []struct {
 		name         string