Skip to content

Instantly share code, notes, and snippets.

@anonhostpi
Last active September 14, 2025 08:42
Show Gist options
  • Select an option

  • Save anonhostpi/e33c2fb4e3282ff75962cf12a2a9af6a to your computer and use it in GitHub Desktop.

Select an option

Save anonhostpi/e33c2fb4e3282ff75962cf12a2a9af6a to your computer and use it in GitHub Desktop.

Revisions

  1. anonhostpi revised this gist Sep 14, 2025. 1 changed file with 522 additions and 47 deletions.
    569 changes: 522 additions & 47 deletions wasm.ps1
    Original 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-Module "Wasmtime"
    # 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)]
    $Input,
    $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, ($Input | Select-Object -First 1))
    return [Wasmtime.Module]::FromStream($Engine, $Name, ($InputObject | Select-Object -First 1))
    }
    return [Wasmtime.Module]::FromBytes($Engine, $Name, $Input)
    return [Wasmtime.Module]::FromBytes($Engine, $Name, $InputObject)
    } Else {
    If($Stream) {
    return [Wasmtime.Module]::FromTextStream($Engine, $Name, ($Input | Select-Object -First 1))
    return [Wasmtime.Module]::FromTextStream($Engine, $Name, ($InputObject | Select-Object -First 1))
    }
    return [Wasmtime.Module]::FromText($Engine, $Name, "$Input")
    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
    [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
    )

    return [Wasmtime.Linker]::new($Engine)
    $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
    [System.Object] $Context = $Null,
    [Wasmtime.WasiConfiguration] $WasiConfiguration = $Null
    )

    If($null -eq $Context){
    return [Wasmtime.Store]::new($Engine)
    $store = If($null -eq $Context){
    [Wasmtime.Store]::new($Engine)
    } else {
    return [Wasmtime.Store]::new($Engine, $Context)
    [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))
    }

    $engine = New-WasmEngine
    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)

    $linker = New-WasmLinker -Engine $engine
    $store = New-WasmStore -Engine $engine # -Context @{}
    $data = New-Object byte[] $entry.Size

    $import = @{
    "say" = @{
    "hello" = {
    Write-Host "Hello there!"
    if ($tar.Read($data, 0, $data.Length) -ne $data.Length) {
    throw "Failed to read full entry: $($entry.Name)"
    }

    $wabt[$name] = $data
    }
    }

    return $wabt
    }
    }

    $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"
    $stdout_file = @{
    Enabled = $false
    Path = New-TemporaryFile
    }
    }
    function New-WasiRuntime {
    $runtime = @{ Engine = New-WasmEngine }

    $module = & {
    $labels = @()
    $functions = $signatures | ForEach-Object {
    $from, $name = $_ -split '\.'
    $label = '$' + (@($from, $name) -join '_')
    $labels += $label
    $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
    }

    return "(func $label (import `"$from`" `"$name`"))"
    $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"
    }
    }
    $calls = & {
    $result = @()
    $labels | ForEach-Object {
    $result += "(call $_)"

    $state.Module = & {
    $labels = @()
    $functions = $signatures | ForEach-Object {
    $from, $name = $_ -split '\.'
    $label = '$' + (@($from, $name) -join '_')
    $labels += $label

    return "(func $label (import `"$from`" `"$name`"))"
    }
    return $result -join " "
    $calls = & {
    $result = @()
    $labels | ForEach-Object {
    $result += "(call $_)"
    }
    return $result -join " "
    }
    $run = "(func (export `"run`") $calls)"
    $functions = @($functions, $run)
    return "(module $($functions -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
    }
    $module = $module | New-WasmModule -Engine $engine -Name "Main"

    $module_instance = $linker.Instantiate($store, $module)
    $module_instance.GetFunction("run").Invoke()
    # $test = Test-Wasm
  2. anonhostpi revised this gist Sep 13, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion wasm.ps1
    Original 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, world!"
    Write-Host "Hello there!"
    }
    }
    }
  3. anonhostpi revised this gist Sep 13, 2025. 1 changed file with 5 additions and 5 deletions.
    10 changes: 5 additions & 5 deletions wasm.ps1
    Original 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)
    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)
    return [Wasmtime.Module]::FromTextStream($Engine, $Name, ($Input | Select-Object -First 1))
    }
    return [Wasmtime.Module]::FromText($Engine, $Name, $Input)
    return [Wasmtime.Module]::FromText($Engine, $Name, "$Input")
    }
    }
    'File' {
    If($Text) {
    return [Wasmtime.Module]::FromFileText($Engine, $Path)
    return [Wasmtime.Module]::FromFileText($Engine, "$Path")
    } Else {
    return [Wasmtime.Module]::FromFile($Engine, $Path)
    return [Wasmtime.Module]::FromFile($Engine, "$Path")
    }
    }
    }
  4. anonhostpi revised this gist Sep 13, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion wasm.ps1
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    # iex (iwr '').content
    # iex (iwr 'https://gist.github.com/anonhostpi/e33c2fb4e3282ff75962cf12a2a9af6a/raw/wasm.ps1').content

    & {
    # Install-Module "Wasmtime"
  5. anonhostpi created this gist Sep 13, 2025.
    182 changes: 182 additions & 0 deletions wasm.ps1
    Original 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()