package wazero

import (
	
	
	

	
	experimentalapi 
	
	
	
	
	internalsock 
	internalsys 
	
	binaryformat 
	
)

// Runtime allows embedding of WebAssembly modules.
//
// The below is an example of basic initialization:
//
//	ctx := context.Background()
//	r := wazero.NewRuntime(ctx)
//	defer r.Close(ctx) // This closes everything this Runtime created.
//
//	mod, _ := r.Instantiate(ctx, wasm)
//
// # Notes
//
//   - This is an interface for decoupling, not third-party implementations.
//     All implementations are in wazero.
//   - Closing this closes any CompiledModule or Module it instantiated.
type Runtime interface {
	// Instantiate instantiates a module from the WebAssembly binary (%.wasm)
	// with default configuration, which notably calls the "_start" function,
	// if it exists.
	//
	// Here's an example:
	//	ctx := context.Background()
	//	r := wazero.NewRuntime(ctx)
	//	defer r.Close(ctx) // This closes everything this Runtime created.
	//
	//	mod, _ := r.Instantiate(ctx, wasm)
	//
	// # Notes
	//
	//   - See notes on InstantiateModule for error scenarios.
	//   - See InstantiateWithConfig for configuration overrides.
	Instantiate(ctx context.Context, source []byte) (api.Module, error)

	// InstantiateWithConfig instantiates a module from the WebAssembly binary
	// (%.wasm) or errs for reasons including exit or validation.
	//
	// Here's an example:
	//	ctx := context.Background()
	//	r := wazero.NewRuntime(ctx)
	//	defer r.Close(ctx) // This closes everything this Runtime created.
	//
	//	mod, _ := r.InstantiateWithConfig(ctx, wasm,
	//		wazero.NewModuleConfig().WithName("rotate"))
	//
	// # Notes
	//
	//   - See notes on InstantiateModule for error scenarios.
	//   - If you aren't overriding defaults, use Instantiate.
	//   - This is a convenience utility that chains CompileModule with
	//     InstantiateModule. To instantiate the same source multiple times,
	//     use CompileModule as InstantiateModule avoids redundant decoding
	//     and/or compilation.
	InstantiateWithConfig(ctx context.Context, source []byte, config ModuleConfig) (api.Module, error)

	// NewHostModuleBuilder lets you create modules out of functions defined in Go.
	//
	// Below defines and instantiates a module named "env" with one function:
	//
	//	ctx := context.Background()
	//	hello := func() {
	//		fmt.Fprintln(stdout, "hello!")
	//	}
	//	_, err := r.NewHostModuleBuilder("env").
	//		NewFunctionBuilder().WithFunc(hello).Export("hello").
	//		Instantiate(ctx, r)
	//
	// Note: empty `moduleName` is not allowed.
	NewHostModuleBuilder(moduleName string) HostModuleBuilder

	// CompileModule decodes the WebAssembly binary (%.wasm) or errs if invalid.
	// Any pre-compilation done after decoding wasm is dependent on RuntimeConfig.
	//
	// There are two main reasons to use CompileModule instead of Instantiate:
	//   - Improve performance when the same module is instantiated multiple times under different names
	//   - Reduce the amount of errors that can occur during InstantiateModule.
	//
	// # Notes
	//
	//   - The resulting module name defaults to what was binary from the custom name section.
	//   - Any pre-compilation done after decoding the source is dependent on RuntimeConfig.
	//
	// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#name-section%E2%91%A0
	CompileModule(ctx context.Context, binary []byte) (CompiledModule, error)

	// InstantiateModule instantiates the module or errs for reasons including
	// exit or validation.
	//
	// Here's an example:
	//	mod, _ := n.InstantiateModule(ctx, compiled, wazero.NewModuleConfig().
	//		WithName("prod"))
	//
	// # Errors
	//
	// While CompiledModule is pre-validated, there are a few situations which
	// can cause an error:
	//   - The module name is already in use.
	//   - The module has a table element initializer that resolves to an index
	//     outside the Table minimum size.
	//   - The module has a start function, and it failed to execute.
	//   - The module was compiled to WASI and exited with a non-zero exit
	//     code, you'll receive a sys.ExitError.
	//   - RuntimeConfig.WithCloseOnContextDone was enabled and a context
	//     cancellation or deadline triggered before a start function returned.
	InstantiateModule(ctx context.Context, compiled CompiledModule, config ModuleConfig) (api.Module, error)

	// CloseWithExitCode closes all the modules that have been initialized in this Runtime with the provided exit code.
	// An error is returned if any module returns an error when closed.
	//
	// Here's an example:
	//	ctx := context.Background()
	//	r := wazero.NewRuntime(ctx)
	//	defer r.CloseWithExitCode(ctx, 2) // This closes everything this Runtime created.
	//
	//	// Everything below here can be closed, but will anyway due to above.
	//	_, _ = wasi_snapshot_preview1.InstantiateSnapshotPreview1(ctx, r)
	//	mod, _ := r.Instantiate(ctx, wasm)
	CloseWithExitCode(ctx context.Context, exitCode uint32) error

	// Module returns an instantiated module in this runtime or nil if there aren't any.
	Module(moduleName string) api.Module

	// Closer closes all compiled code by delegating to CloseWithExitCode with an exit code of zero.
	api.Closer
}

// NewRuntime returns a runtime with a configuration assigned by NewRuntimeConfig.
func ( context.Context) Runtime {
	return NewRuntimeWithConfig(, NewRuntimeConfig())
}

// NewRuntimeWithConfig returns a runtime with the given configuration.
func ( context.Context,  RuntimeConfig) Runtime {
	 := .(*runtimeConfig)
	 := .engineKind
	 := .newEngine
	if  == engineKindAuto {
		if platform.CompilerSupports(.enabledFeatures) {
			 = engineKindCompiler
		} else {
			 = engineKindInterpreter
		}
	}
	if  == nil {
		if  == engineKindCompiler {
			 = wazevo.NewEngine
		} else {
			 = interpreter.NewEngine
		}
	}
	var  wasm.Engine
	var  *cache
	if  := .cache;  != nil {
		// If the Cache is configured, we share the engine.
		 = .(*cache)
		 = .initEngine(, , , .enabledFeatures)
	} else {
		// Otherwise, we create a new engine.
		 = (, .enabledFeatures, nil)
	}
	 := wasm.NewStore(.enabledFeatures, )
	return &runtime{
		cache:                 ,
		store:                 ,
		enabledFeatures:       .enabledFeatures,
		memoryLimitPages:      .memoryLimitPages,
		memoryCapacityFromMax: .memoryCapacityFromMax,
		dwarfDisabled:         .dwarfDisabled,
		storeCustomSections:   .storeCustomSections,
		ensureTermination:     .ensureTermination,
	}
}

// runtime allows decoupling of public interfaces from internal representation.
type runtime struct {
	store                 *wasm.Store
	cache                 *cache
	enabledFeatures       api.CoreFeatures
	memoryLimitPages      uint32
	memoryCapacityFromMax bool
	dwarfDisabled         bool
	storeCustomSections   bool

	// closed is the pointer used both to guard moduleEngine.CloseWithExitCode and to store the exit code.
	//
	// The update value is 1 + exitCode << 32. This ensures an exit code of zero isn't mistaken for never closed.
	//
	// Note: Exclusively reading and updating this with atomics guarantees cross-goroutine observations.
	// See /RATIONALE.md
	closed atomic.Uint64

	ensureTermination bool
}

// Module implements Runtime.Module.
func ( *runtime) ( string) api.Module {
	if len() == 0 {
		return nil
	}
	 := .store.Module()
	if  == nil {
		return nil
	} else if .Source.IsHostModule {
		return hostModuleInstance{}
	}
	return 
}

// CompileModule implements Runtime.CompileModule
func ( *runtime) ( context.Context,  []byte) (CompiledModule, error) {
	if  := .failIfClosed();  != nil {
		return nil, 
	}

	,  := binaryformat.DecodeModule(, .enabledFeatures,
		.memoryLimitPages, .memoryCapacityFromMax, !.dwarfDisabled, .storeCustomSections)
	if  != nil {
		return nil, 
	} else if  = .Validate(.enabledFeatures);  != nil {
		// TODO: decoders should validate before returning, as that allows
		// them to err with the correct position in the wasm binary.
		return nil, 
	}

	// Now that the module is validated, cache the memory definitions.
	// TODO: lazy initialization of memory definition.
	.BuildMemoryDefinitions()

	 := &compiledModule{module: , compiledEngine: .store.Engine}

	// typeIDs are static and compile-time known.
	,  := .store.GetFunctionTypeIDs(.TypeSection)
	if  != nil {
		return nil, 
	}
	.typeIDs = 

	,  := buildFunctionListeners(, )
	if  != nil {
		return nil, 
	}
	.AssignModuleID(, , .ensureTermination)
	if  = .store.Engine.CompileModule(, , , .ensureTermination);  != nil {
		return nil, 
	}
	return , nil
}

func buildFunctionListeners( context.Context,  *wasm.Module) ([]experimentalapi.FunctionListener, error) {
	// Test to see if internal code are using an experimental feature.
	 := .Value(expctxkeys.FunctionListenerFactoryKey{})
	if  == nil {
		return nil, nil
	}
	 := .(experimentalapi.FunctionListenerFactory)
	 := .ImportFunctionCount
	 := make([]experimentalapi.FunctionListener, len(.FunctionSection))
	for  := 0;  < len(); ++ {
		[] = .NewFunctionListener(.FunctionDefinition(uint32() + ))
	}
	return , nil
}

// failIfClosed returns an error if CloseWithExitCode was called implicitly (by Close) or explicitly.
func ( *runtime) () error {
	if  := .closed.Load();  != 0 {
		return fmt.Errorf("runtime closed with exit_code(%d)", uint32(>>32))
	}
	return nil
}

// Instantiate implements Runtime.Instantiate
func ( *runtime) ( context.Context,  []byte) (api.Module, error) {
	return .InstantiateWithConfig(, , NewModuleConfig())
}

// InstantiateWithConfig implements Runtime.InstantiateWithConfig
func ( *runtime) ( context.Context,  []byte,  ModuleConfig) (api.Module, error) {
	if ,  := .CompileModule(, );  != nil {
		return nil, 
	} else {
		.(*compiledModule).closeWithModule = true
		return .InstantiateModule(, , )
	}
}

// InstantiateModule implements Runtime.InstantiateModule.
func ( *runtime) (
	 context.Context,
	 CompiledModule,
	 ModuleConfig,
) ( api.Module,  error) {
	if  = .failIfClosed();  != nil {
		return nil, 
	}

	 := .(*compiledModule)
	 := .(*moduleConfig)

	// Only add guest module configuration to guests.
	if !.module.IsHostModule {
		if ,  := .Value(internalsock.ConfigKey{}).(*internalsock.Config);  {
			.sockConfig = 
		}
	}

	var  *internalsys.Context
	if ,  = .toSysContext();  != nil {
		return nil, 
	}

	 := .name
	if !.nameSet && .module.NameSection != nil && .module.NameSection.ModuleName != "" {
		 = .module.NameSection.ModuleName
	}

	// Instantiate the module.
	,  = .store.Instantiate(, .module, , , .typeIDs)
	if  != nil {
		// If there was an error, don't leak the compiled module.
		if .closeWithModule {
			_ = .Close() // don't overwrite the error
		}
		return nil, 
	}

	if ,  := .Value(expctxkeys.CloseNotifierKey{}).(experimentalapi.CloseNotifier);  {
		.(*wasm.ModuleInstance).CloseNotifier = 
	}

	// Attach the code closer so that anything afterward closes the compiled
	// code when closing the module.
	if .closeWithModule {
		.(*wasm.ModuleInstance).CodeCloser = 
	}

	// Now, invoke any start functions, failing at first error.
	for ,  := range .startFunctions {
		 := .ExportedFunction()
		if  == nil {
			continue
		}
		if _,  = .Call();  != nil {
			_ = .Close() // Don't leak the module on error.

			if ,  := .(*sys.ExitError);  {
				if .ExitCode() == 0 { // Don't err on success.
					 = nil
				}
				return // Don't wrap an exit error
			}
			 = fmt.Errorf("module[%s] function[%s] failed: %w", , , )
			return
		}
	}
	return
}

// Close implements api.Closer embedded in Runtime.
func ( *runtime) ( context.Context) error {
	return .CloseWithExitCode(, 0)
}

// CloseWithExitCode implements Runtime.CloseWithExitCode
//
// Note: it also marks the internal `closed` field
func ( *runtime) ( context.Context,  uint32) error {
	 := uint64(1) + uint64()<<32 // Store exitCode as high-order bits.
	if !.closed.CompareAndSwap(0, ) {
		return nil
	}
	 := .store.CloseWithExitCode(, )
	if .cache == nil {
		// Close the engine if the cache is not configured, which means that this engine is scoped in this runtime.
		if  := .store.Engine.Close();  != nil {
			return 
		}
	}
	return 
}