Last active
September 14, 2025 08:42
-
-
Save anonhostpi/e33c2fb4e3282ff75962cf12a2a9af6a to your computer and use it in GitHub Desktop.
Revisions
-
anonhostpi revised this gist
Sep 14, 2025 . 1 changed file with 522 additions and 47 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,7 +1,7 @@ # iex (iwr 'https://gist.github.com/anonhostpi/e33c2fb4e3282ff75962cf12a2a9af6a/raw/wasm.ps1').content & { # Install-Package "Wasmtime" -ProviderName NuGet $package = Get-Package -Name "Wasmtime" $directory = $package.Source | Split-Path @@ -44,11 +44,16 @@ function New-WasmModule { [Parameter(Mandatory=$true)] [Wasmtime.Engine] $Engine, [Parameter(ParameterSetName='URL', Mandatory=$true)] [string] $Url, [Parameter(ParameterSetName='URL')] [Parameter(ParameterSetName='InputObject', Mandatory=$true)] [string] $Name, [Parameter(ParameterSetName='InputObject', Mandatory=$true, ValueFromPipeline=$true)] $InputObject, [Parameter(ParameterSetName='URL')] [Parameter(ParameterSetName='InputObject')] [switch] $Binary, # Default is .wat (text) [Parameter(ParameterSetName='InputObject')] @@ -57,22 +62,98 @@ function New-WasmModule { [Parameter(ParameterSetName='File', Mandatory=$true, ValueFromPipeline=$true)] [string] $Path, [Parameter(ParameterSetName='URL')] [Parameter(ParameterSetName='File')] [switch] $Text # Default is .wasm (binary) ) $uri = $Url $URLProvided = & { If( $PSCmdlet.ParameterSetName -eq 'URL' ) { return $true } If( $PSCmdlet.ParameterSetName -eq 'InputObject' ) { If( [string]::IsNullOrWhiteSpace($InputObject) ){ return $false } Try { $uri = [System.Uri]::new($InputObject) return $uri.IsAbsoluteUri -and ($uri.Scheme -in @('http', 'https')) } Catch { return $false } } If( $PSCmdlet.ParameterSetName -eq 'File' ) { If( [string]::IsNullOrWhiteSpace($Path) ){ return $false } Try { return -not (Test-Path $Path -PathType Leaf) } Catch {} Try { $uri = [System.Uri]::new($Path) return $uri.IsAbsoluteUri -and ($uri.Scheme -eq 'file') } Catch { return $false } } } If( $URLProvided ){ If([string]::IsNullOrEmpty($Name)){ $Name = [System.IO.Path]::GetFileNameWithoutExtension("$uri") } $request = [System.Net.WebRequest]::Create("$uri") $response = $request.GetResponse() $IsBinary = & { $switches = @([bool]$Binary, [bool]$Text) | Where-Object { $_ -eq $true } If($switches.Count -eq 1){ return $Binary } $extension = [System.IO.Path]::GetExtension("$uri").ToLowerInvariant() switch ($extension) { '.wasm' { return $true } '.wat' { return $false } default { switch($response.ContentType.ToLowerInvariant()) { 'text/plain' { return $false } 'text/wat' { return $false } 'application/wat' { return $false } default { return $true } # assume anything else is binary } } } } [System.IO.Stream] $stream = $response.GetResponseStream() If($IsBinary) { return [Wasmtime.Module]::FromStream($Engine, $Name, $stream) } Else { return [Wasmtime.Module]::FromTextStream($Engine, $Name, $stream) } } switch ($PSCmdlet.ParameterSetName) { 'InputObject' { If($Binary) { If($Stream) { return [Wasmtime.Module]::FromStream($Engine, $Name, ($InputObject | Select-Object -First 1)) } return [Wasmtime.Module]::FromBytes($Engine, $Name, $InputObject) } Else { If($Stream) { return [Wasmtime.Module]::FromTextStream($Engine, $Name, ($InputObject | Select-Object -First 1)) } return [Wasmtime.Module]::FromText($Engine, $Name, "$InputObject") } } 'File' { @@ -89,25 +170,203 @@ function New-WasmLinker { [CmdletBinding()] param( [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [Wasmtime.Engine] $Engine, [switch] $Wasi ) $linker = [Wasmtime.Linker]::new($Engine) If($Wasi) { $linker.DefineWasi() | Out-Null } return $linker } function New-WasiConfig { [CmdletBinding()] param( $ArgumentList, [switch] $InheritArguments, [System.Collections.IDictionary] $EnvironmentVariables, [switch] $InheritEnvironment, [System.Collections.IDictionary] $DirectoryMounts, [string] $ErrorFile, [ValidateScript({ if ($PSBoundParameters.ContainsKey('ErrorFile')) { throw "You cannot use -ErrorFile and -InheritStandardError together." } $true })] [switch] $InheritStandardError, [string] $OutputFile, [ValidateScript({ if ($PSBoundParameters.ContainsKey('OutputFile')) { throw "You cannot use -OutputFile and -InheritStandardOutput together." } $true })] [switch] $InheritStandardOutput, [string] $InputFile, [ValidateScript({ if ($PSBoundParameters.ContainsKey('InputFile')) { throw "You cannot use -InputFile and -InheritStandardInput together." } $true })] [switch] $InheritStandardInput ) $config = [Wasmtime.WasiConfiguration]::new() if ($InheritArguments) { $config.WithInheritedArgs() | Out-Null } $a = $ArgumentList | ForEach-Object { "$_" } If( $a.Count -eq 1 ){ $config.WithArg(($a | Select-Object -First 1)) | Out-Null } If( $a.Count -gt 1 ){ $a = $a | ForEach-Object { $_ | ConvertTo-Json -Compress } $a = $a -join "," Invoke-Expression "`$config.WithArgs($a) | Out-Null" } if ($InheritEnvironment) { $config.WithInheritedEnvironment() | Out-Null } If( $EnvironmentVariables.Count ){ $tuples = $EnvironmentVariables.GetEnumerator() | ForEach-Object { [System.ValueTuple[string,string]]::new($_.Key, $_.Value) } $config.WithEnvironmentVariables($tuples) | Out-Null } if ($InheritStandardError) { $config.WithInheritedStandardError() | Out-Null } elseif( Test-Path -PathType Leaf $ErrorFile ) { $config.WithStandardError("$ErrorFile") | Out-Null } if ($InheritStandardOutput) { $config.WithInheritedStandardOutput() | Out-Null } elseif( Test-Path -PathType Leaf $OutputFile ) { $config.WithStandardOutput("$OutputFile") | Out-Null } if ($InheritStandardInput) { $config.WithInheritedStandardInput() | Out-Null } elseif( Test-Path -PathType Leaf $InputFile ) { $config.WithStandardInput("$InputFile") | Out-Null } If( $DirectoryMounts.Count ){ $DirectoryMounts.GetEnumerator() | ForEach-Object { $dirs = @{ Host = $_.Key Guest = $_.Value } $perms = & { If( $dirs.Guest -is [string] ){ return @{ dir = [Wasmtime.WasiDirectoryPermissions]::Read file = [Wasmtime.WasiFilePermissions]::Read } } $perm_dir, $perm_file = (& { $user_provided = $dirs.Guest.Permissions $has_perms = $null -ne $user_provided If( -not $has_perms ){ return @("Read", "Read") } $has_dir = $null -ne $user_provided.Directory $has_file = $null -ne $user_provided.File If( $has_dir -or $has_file ){ $count = [int]$has_dir + [int]$has_file If( $count -eq 2 ){ return @($user_provided.Directory, $user_provided.File) } If( $has_dir ){ return @($user_provided.Directory, "Read") } If( $has_file ){ return @("Read", $user_provided.File) } } return @($user_provided, $user_provided) }) $full = [System.IO.Path]::GetFullPath($dirs.Guest.Directory) $no_drive = $full -replace '^[a-zA-Z]:', '' $unix = $no_drive.Replace("\", "/") $dirs.Guest = $unix return @{ dir = (& { switch("$perm_dir"){ "Read" { [Wasmtime.WasiDirectoryPermissions]::Read } "R" { [Wasmtime.WasiDirectoryPermissions]::Read } "Write" { [Wasmtime.WasiDirectoryPermissions]::Write } "W" { [Wasmtime.WasiDirectoryPermissions]::Write } "ReadWrite" { [Wasmtime.WasiDirectoryPermissions]::Write } "RW" { [Wasmtime.WasiDirectoryPermissions]::Write } "$([int]([Wasmtime.WasiDirectoryPermissions]::Read))" { [Wasmtime.WasiDirectoryPermissions]::Read } "$([int]([Wasmtime.WasiDirectoryPermissions]::Write))" { [Wasmtime.WasiDirectoryPermissions]::Write } default { [Wasmtime.WasiDirectoryPermissions]::Read } } }) file = (& { switch("$perm_file"){ "Read" { [Wasmtime.WasiFilePermissions]::Read } "R" { [Wasmtime.WasiFilePermissions]::Read } "Write" { [Wasmtime.WasiFilePermissions]::Write } "W" { [Wasmtime.WasiFilePermissions]::Write } "ReadWrite" { [Wasmtime.WasiFilePermissions]::Write } "RW" { [Wasmtime.WasiFilePermissions]::Write } "$([int]([Wasmtime.WasiFilePermissions]::Read))" { [Wasmtime.WasiFilePermissions]::Read } "$([int]([Wasmtime.WasiFilePermissions]::Write))" { [Wasmtime.WasiFilePermissions]::Write } default { [Wasmtime.WasiFilePermissions]::Read } } }) } } $config.WithPreopenedDirectory("$($dirs.Host)", "$($dirs.Guest)", $perms.dir, $perms.file) | Out-Null } } return $config } function New-WasmStore { [CmdletBinding()] param( [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [Wasmtime.Engine] $Engine, [System.Object] $Context = $Null, [Wasmtime.WasiConfiguration] $WasiConfiguration = $Null ) $store = If($null -eq $Context){ [Wasmtime.Store]::new($Engine) } else { [Wasmtime.Store]::new($Engine, $Context) } If($null -ne $WasiConfiguration) { $store.SetWasiConfiguration($WasiConfiguration) } return $store } # NOTE: does not support return values. To return values, declare the function explicitly! @@ -132,51 +391,267 @@ function New-WasmFunction { return [Wasmtime.Function]::FromCallback($Store, (Invoke-Expression $cb)) } function Get-WasiProxyModule { [CmdletBinding()] param( [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [Wasmtime.Engine] $Engine ) New-WasmModule -Engine $Engine -Url 'https://github.com/bytecodealliance/wasmtime/releases/download/v36.0.2/wasi_snapshot_preview1.proxy.wasm' } function Get-WasmLibraryName { [CmdletBinding()] param() return ([Wasmtime.Engine].DeclaredFields | Where-Object { $_.Name -eq "LibraryName" }).GetValue($null) } New-Module "Wabt" -ScriptBlock { # Cache: $wabt = [ordered]@{} function Get-WabtModules { If( $wabt.Keys.Count -eq 0 ){ & { # For temporary tar support # - We can later swap this out for a wasm implementation # Install-Package "SharpZipLib" -RequiredVersion 1.4.2 -ProviderName NuGet $package = Get-Package -Name "SharpZipLib" $directory = $package.Source | Split-Path Add-Type -Path "$directory\lib\netstandard2.1\ICSharpCode.SharpZipLib.dll" } $build = "https://github.com/WebAssembly/wabt/releases/download/1.0.37/wabt-1.0.37-wasi.tar.gz" $request = [System.Net.WebRequest]::Create($build) $response = $request.GetResponse() $stream = $response.GetResponseStream() $gzip = [ICSharpCode.SharpZipLib.GZip.GZipInputStream]::new($stream) $tar = [ICSharpCode.SharpZipLib.Tar.TarInputStream]::new($gzip) while ($true) { $entry = $tar.GetNextEntry() if ($null -eq $entry) { break } if ($entry.IsDirectory) { continue } $path = $entry.Name if (-not ($path.TrimStart("\/").Replace("\", "/") -like "wabt-1.0.37/bin/*")) { continue } $name = [System.IO.Path]::GetFileNameWithoutExtension($path) $data = New-Object byte[] $entry.Size if ($tar.Read($data, 0, $data.Length) -ne $data.Length) { throw "Failed to read full entry: $($entry.Name)" } $wabt[$name] = $data } } return $wabt } $stdout_file = @{ Enabled = $false Path = New-TemporaryFile } function New-WasiRuntime { $runtime = @{ Engine = New-WasmEngine } $wasi_params = @{ ArgumentList = $args InheritEnvironment = $true InheritStandardError = $true InheritStandardInput = $true DirectoryMounts = @{ "$(Get-Location)" = @{ Directory = "/" Permissions = @{ Directory = "Read" File = "Read" } } } } If( $stdout_file.Enabled ){ $wasi_params.OutputFile = $stdout_file.Path } Else { $wasi_params.InheritStandardOutput = $true } $runtime.Store = New-WasmStore ` -Engine $runtime.Engine ` -WasiConfiguration (New-WasiConfig @wasi_params) $runtime.Linker = New-WasmLinker -Engine $runtime.Engine -Wasi return $runtime } function ConvertTo-PascalCase { param( [Parameter(Mandatory)] [string]$InputString ) # Step 1: split on non-alphanumeric chars $segments = $InputString -split '[^a-zA-Z0-9]+' | Where-Object { $_ } $parts = foreach ($seg in $segments) { # Step 2: split segment into alternating letter/digit groups [regex]::Split($seg, "(?<=\d)(?=[a-zA-Z])") | Where-Object { $_ } } # Step 3: capitalize each part if it starts with a letter $pascal = ($parts | ForEach-Object { if ($_ -match '^[a-zA-Z]') { $_.Substring(0,1).ToUpper() + $_.Substring(1).ToLower() } else { $_ } }) -join '' return $pascal } $mapping = @{} foreach($name in (Get-WabtModules).Keys) { $functionname = ConvertTo-PascalCase $name $functionname = $functionname.Replace("2","To") $functionname = "Invoke-$functionname" $mapping[$functionname] = $name Set-Item -Path "function:$functionname" -Value { $binary_name = $mapping[$MyInvocation.MyCommand.Name] Clear-Content -Path $stdout_file.Path -ErrorAction SilentlyContinue $stdout_file.Enabled = $true $runtime = New-WasiRuntime $binary_name @args Try { $runtime.Linker.Instantiate( $runtime.Store, [Wasmtime.Module]::FromBytes( $runtime.Engine, $binary_name, $wabt."$binary_name" ) ).GetFunction("_start").Invoke() | Out-Null } Catch { Write-Warning "Some WASM runtime error occurred. Check the output for details or `$Error." } return Get-Content -Path $stdout_file.Path -ErrorAction SilentlyContinue } Set-Item -Path "function:$functionname`Live" -Value { # We may be able to fix this at a later point by defining overwriting the builtin fd_write behavior # This may be possible with AllowShadowing set to true Write-Warning "Live output can not be captured to a variable or piped!" Write-Host "- Wasmtime internally pipes directly to stdout instead of piping back to C#/PowerShell." Write-Host "- To capture output, use $($MyInvocation.MyCommand.Name.Replace('Live','')) instead." Write-Host $binary_name = $mapping[$MyInvocation.MyCommand.Name.Replace("Live","")] $stdout_file.Enabled = $false $runtime = New-WasiRuntime $binary_name @args Try { $runtime.Linker.Instantiate( $runtime.Store, [Wasmtime.Module]::FromBytes( $runtime.Engine, $binary_name, $wabt."$binary_name" ) ).GetFunction("_start").Invoke() | Out-Null } Catch { Write-Warning "Some WASM runtime error occurred. Check the output for details or `$Error." } } } Export-ModuleMember -Function ($mapping.Keys | % { $_ } | % { "$_","${_}Live" }) } | Import-Module function Test-Wasm { [CmdletBinding()] param( [Parameter(ValueFromPipeline=$true)] [System.Collections.IDictionary] $Imports = @{ "say" = @{ "hello" = { Write-Host "Hello from wasm!" } } } ) $state = @{ Engine = New-WasmEngine } $state.Linker = New-WasmLinker -Engine $state.Engine $state.Store = New-WasmStore ` -Engine $state.Engine ` -WasiConfiguration (New-WasiConfig ` -InheritArguments ` -InheritEnvironment ` -InheritStandardError ` -InheritStandardOutput ` -InheritStandardInput ` -DirectoryMounts @{ "$(Get-Location)" = @{ Directory = "/" Permissions = @{ Directory = "Read" File = "Read" } } } ) # -Context @{} $signatures = @() $Imports.GetEnumerator() | ForEach-Object { $from = $_.Key $_.Value.GetEnumerator() | ForEach-Object { $name = $_.Key $function = New-WasmFunction -Store $state.Store -Callback $_.Value $state.Linker.Define($from, $name, $function) $signatures += "$from.$name" } } $state.Module = & { $labels = @() $functions = $signatures | ForEach-Object { $from, $name = $_ -split '\.' $label = '$' + (@($from, $name) -join '_') $labels += $label return "(func $label (import `"$from`" `"$name`"))" } $calls = & { $result = @() $labels | ForEach-Object { $result += "(call $_)" } return $result -join " " } $run = "(func (export `"run`") $calls)" $functions = @($functions, $run) return "(module $($functions -join " "))" } $state.Module = $state.Module | New-WasmModule -Engine $state.Engine -Name "Main" $state.ModuleInstance = $state.Linker.Instantiate($state.Store, $state.Module) $state.ModuleInstance.GetFunction("run").Invoke() $state.LibraryName = Get-WasmLibraryName $state.WASI = Get-WasiProxyModule -Engine $state.Engine return $state } # $test = Test-Wasm -
anonhostpi revised this gist
Sep 13, 2025 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -140,7 +140,7 @@ $store = New-WasmStore -Engine $engine # -Context @{} $import = @{ "say" = @{ "hello" = { Write-Host "Hello there!" } } } -
anonhostpi revised this gist
Sep 13, 2025 . 1 changed file with 5 additions and 5 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -65,21 +65,21 @@ function New-WasmModule { 'InputObject' { If($Binary) { If($Stream) { return [Wasmtime.Module]::FromStream($Engine, $Name, ($Input | Select-Object -First 1)) } return [Wasmtime.Module]::FromBytes($Engine, $Name, $Input) } Else { If($Stream) { return [Wasmtime.Module]::FromTextStream($Engine, $Name, ($Input | Select-Object -First 1)) } return [Wasmtime.Module]::FromText($Engine, $Name, "$Input") } } 'File' { If($Text) { return [Wasmtime.Module]::FromFileText($Engine, "$Path") } Else { return [Wasmtime.Module]::FromFile($Engine, "$Path") } } } -
anonhostpi revised this gist
Sep 13, 2025 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,4 +1,4 @@ # iex (iwr 'https://gist.github.com/anonhostpi/e33c2fb4e3282ff75962cf12a2a9af6a/raw/wasm.ps1').content & { # Install-Module "Wasmtime" -
anonhostpi created this gist
Sep 13, 2025 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,182 @@ # iex (iwr '').content & { # Install-Module "Wasmtime" $package = Get-Package -Name "Wasmtime" $directory = $package.Source | Split-Path $runtime = "win-x64" # "win/linux/osx-arm64/x64" $native = "$directory\runtimes\$runtime\native" | Resolve-Path $env:PATH += ";$native" Add-Type -Path "$directory\lib\netstandard2.1\Wasmtime.Dotnet.dll" } function New-WasmEngine { [CmdletBinding()] param( [Parameter(ValueFromPipeline=$true)] [Wasmtime.Config] $config = $null ) If ($null -eq $config) { return [Wasmtime.Engine]::new() } else { return [Wasmtime.Engine]::new($config) } } function ConvertTo-Wasm { [CmdletBinding()] param( [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [string] $Text ) return [Wasmtime.Module]::ConvertText($Text) } function New-WasmModule { [CmdletBinding(DefaultParameterSetName='InputObject')] param ( [Parameter(Mandatory=$true)] [Wasmtime.Engine] $Engine, [Parameter(ParameterSetName='InputObject', Mandatory=$true)] [string] $Name, [Parameter(ParameterSetName='InputObject', Mandatory=$true, ValueFromPipeline=$true)] $Input, [Parameter(ParameterSetName='InputObject')] [switch] $Binary, # Default is .wat (text) [Parameter(ParameterSetName='InputObject')] [switch] $Stream, [Parameter(ParameterSetName='File', Mandatory=$true, ValueFromPipeline=$true)] [string] $Path, [Parameter(ParameterSetName='File')] [switch] $Text # Default is .wasm (binary) ) switch ($PSCmdlet.ParameterSetName) { 'InputObject' { If($Binary) { If($Stream) { return [Wasmtime.Module]::FromStream($Engine, $Name, $Input) } return [Wasmtime.Module]::FromBytes($Engine, $Name, $Input) } Else { If($Stream) { return [Wasmtime.Module]::FromTextStream($Engine, $Name, $Input) } return [Wasmtime.Module]::FromText($Engine, $Name, $Input) } } 'File' { If($Text) { return [Wasmtime.Module]::FromFileText($Engine, $Path) } Else { return [Wasmtime.Module]::FromFile($Engine, $Path) } } } } function New-WasmLinker { [CmdletBinding()] param( [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [Wasmtime.Engine] $Engine ) return [Wasmtime.Linker]::new($Engine) } function New-WasmStore { [CmdletBinding()] param( [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [Wasmtime.Engine] $Engine, [System.Object] $Context = $Null ) If($null -eq $Context){ return [Wasmtime.Store]::new($Engine) } else { return [Wasmtime.Store]::new($Engine, $Context) } } # NOTE: does not support return values. To return values, declare the function explicitly! function New-WasmFunction { [CmdletBinding()] param( [Parameter(Mandatory=$true)] [Wasmtime.Store] $Store, [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [scriptblock] $Callback, [Type[]] $Parameters = (&{ $callback.Ast.ParamBlock.Parameters.StaticType }) ) $cb = If($Parameters.Count -gt 0) { "[System.Action[$(($Parameters | ForEach-Object { $_.FullName }) -join ',')]] `$Callback" } Else { "[System.Action] `$Callback" } return [Wasmtime.Function]::FromCallback($Store, (Invoke-Expression $cb)) } $engine = New-WasmEngine $linker = New-WasmLinker -Engine $engine $store = New-WasmStore -Engine $engine # -Context @{} $import = @{ "say" = @{ "hello" = { Write-Host "Hello, world!" } } } $signatures = @() $import.GetEnumerator() | ForEach-Object { $from = $_.Key $_.Value.GetEnumerator() | ForEach-Object { $name = $_.Key $function = New-WasmFunction -Store $store -Callback $_.Value $linker.Define($from, $name, $function) $signatures += "$from.$name" } } $module = & { $labels = @() $functions = $signatures | ForEach-Object { $from, $name = $_ -split '\.' $label = '$' + (@($from, $name) -join '_') $labels += $label return "(func $label (import `"$from`" `"$name`"))" } $calls = & { $result = @() $labels | ForEach-Object { $result += "(call $_)" } return $result -join " " } $run = "(func (export `"run`") $calls)" $functions = @($functions, $run) return "(module $($functions -join " "))" } $module = $module | New-WasmModule -Engine $engine -Name "Main" $module_instance = $linker.Instantiate($store, $module) $module_instance.GetFunction("run").Invoke()