cat /dev/random meh

package main

import (
	"context"
	_ "embed"
	"flag"
	"fmt"
	"log"
	"os"

	"github.com/tetratelabs/wazero"
	"github.com/tetratelabs/wazero/api"
	"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
)

var (
	wasmFile = flag.String("wasm", "greet.wasm", "path to the WebAssembly file")
	say      = flag.String("say", "Hello", "what to say")
)

func logString(_ context.Context, m api.Module, offset, byteCount uint32) {
	buf, ok := m.Memory().Read(offset, byteCount)
	if !ok {
		log.Panicf("Memory.Read(%d, %d) out of range", offset, byteCount)
	}
	fmt.Println(string(buf))
}

func callHost(_ context.Context, m api.Module, offset, byteCount uint32) uint64 {
	buf, ok := m.Memory().Read(offset, byteCount)
	if !ok {
		log.Panicf("Memory.Read(%d, %d) out of range", offset, byteCount)
	}

	// TODO
	ret := "callHost " + string(buf)

	// Allocate memory for the return value. Wasm component should free it.
	ptr, err := m.ExportedFunction("malloc").Call(context.Background(), uint64(len(ret)))
	if err != nil {
		log.Panicln(err)
	}
	if !m.Memory().Write(uint32(ptr[0]), []byte(ret)) {
		log.Panicf("Memory.Write(%d, %d) out of range of memory size %d", ptr[0], len(ret), m.Memory().Size())
	}
	retPtr := uint32(ptr[0])
	retSize := uint32(len(ret))
	return uint64(retPtr)<<32 | uint64(retSize)
}

func main() {
	flag.Parse()

	// Read the WebAssembly file.
	wasm, err := os.ReadFile(*wasmFile)
	if err != nil {
		log.Panicln(err)
	}

	// Choose the context to use for function calls.
	ctx := context.Background()

	// Create a new WebAssembly Runtime.
	r := wazero.NewRuntime(ctx)
	defer r.Close(ctx) // This closes everything this Runtime created.

	_, err = r.NewHostModuleBuilder("env").
		NewFunctionBuilder().WithFunc(callHost).Export("call_host").
		NewFunctionBuilder().WithFunc(logString).Export("log").
		Instantiate(ctx)
	if err != nil {
		log.Panicln(err)
	}

	// Note: testdata/greet.go doesn't use WASI, but TinyGo needs it to
	// implement functions such as panic.
	wasi_snapshot_preview1.MustInstantiate(ctx, r)

	// Instantiate a WebAssembly module that imports the "log" function defined
	// in "env" and exports "memory" and functions we'll use in this example.
	mod, err := r.Instantiate(ctx, wasm)
	if err != nil {
		log.Panicln(err)
	}

	doFunc := mod.ExportedFunction("do")
	// These are undocumented, but exported. See tinygo-org/tinygo#2788
	malloc := mod.ExportedFunction("malloc")
	free := mod.ExportedFunction("free")

	argSize := uint64(len(*say))
	mallocRet, err := malloc.Call(context.Background(), argSize)
	if err != nil {
		log.Panicln(err)
	}
	argPtr := uint32(mallocRet[0])

	if !mod.Memory().Write(uint32(argPtr), []byte(*say)) {
		log.Panicf("Memory.Write(%d, %d) out of range of memory size %d",
			argPtr, argSize, mod.Memory().Size())
	}

	ptrSize, err := doFunc.Call(ctx, uint64(argPtr), argSize)
	if err != nil {
		log.Panicln(err)
	}

	retPtr := uint32(ptrSize[0] >> 32)
	retSize := uint32(ptrSize[0])

	// This pointer is managed by TinyGo, but TinyGo is unaware of external usage.
	// So, we have to free it when finished
	if retPtr != 0 {
		defer func() {
			_, err := free.Call(ctx, uint64(retPtr))
			if err != nil {
				log.Panicln(err)
			}
		}()
	}

	// The pointer is a linear memory offset, which is where we write the name.
	if bytes, ok := mod.Memory().Read(retPtr, retSize); !ok {
		log.Panicf("Memory.Read(%d, %d) out of range of memory size %d",
			retPtr, retSize, mod.Memory().Size())
	} else {
		fmt.Println("go >>", string(bytes))
	}
}

#!/bin/sh
#
tinygo build -o plugin.wasm -scheduler=none --no-debug -target=wasi main.go

package main

// #include <stdlib.h>
import "C"
import (
	"encoding/json"
	"unsafe"
)

/* helper functions */

//go:wasmimport env call_host
func _callHost(paramPtr, paramSize uint32) uint64
func callHost(buf string) string {
	ptr, size := gostr_to_ptr(buf)
	ret := _callHost(ptr, size)
	retPtr, retSize := unpack_uint64_to_uint32(ret)
	if retPtr != 0 {
		defer free_ptr(retPtr)
	}
	return ptr_to_gostr(retPtr, retSize)
}

//go:wasmimport env log
func _log(ptr, size uint32)

func log(s string) {
	ptr, size := gostr_to_ptr(s)
	_log(ptr, size)
}

//export do
func _do(ptr, size uint32) (ptrSize uint64) {
	// param's memory is managed by the host
	param := ptr_to_gostr(ptr, size)
	ret := do(param)
	// need to dup the string because the host will free the pointer
	ptr, size = dup_gostr(ret)
	return (uint64(ptr) << uint64(32)) | uint64(size)
}

// ptr_to_gostr returns a string from WebAssembly compatible numeric types
// representing its pointer and length.
func ptr_to_gostr(ptr uint32, size uint32) string {
	return unsafe.String((*byte)(unsafe.Pointer(uintptr(ptr))), size)
}

// gostr_to_ptr returns a pointer and size pair for the given string in a way
// compatible with WebAssembly numeric types.
// The returned pointer aliases the string hence the string must be kept alive
// until ptr is no longer needed.
func gostr_to_ptr(s string) (uint32, uint32) {
	ptr := unsafe.Pointer(unsafe.StringData(s))
	return uint32(uintptr(ptr)), uint32(len(s))
}

// dup_gostr returns a pointer and size pair for the given string in a way
// The pointer is not automatically managed by TinyGo hence it must be freed by the host.
func dup_gostr(s string) (uint32, uint32) {
	size := C.ulong(len(s))
	ptr := unsafe.Pointer(C.malloc(size))
	copy(unsafe.Slice((*byte)(ptr), size), s)
	return uint32(uintptr(ptr)), uint32(size)
}

// free_ptr frees the given pointer.
func free_ptr(ptr uint32) {
	if ptr == 0 {
		return
	}
	C.free(unsafe.Pointer(uintptr(ptr)))
}

func unpack_uint64_to_uint32(v uint64) (ptr uint32, size uint32) {
	return uint32(v >> 32), uint32(v)
}

func pack_uint32_to_uint64(ptr, size uint32) uint64 {
	return (uint64(ptr) << 32) | uint64(size)
}

func uint64_to_gostr(v uint64) (s string, ptr uint32) {
	ptr, size := unpack_uint64_to_uint32(v)
	return ptr_to_gostr(ptr, size), ptr
}

/* main functions */

func do(buf string) string {
	jsonBuf := []byte(buf)
	var data map[string]interface{}
	err := json.Unmarshal(jsonBuf, &data)
	if err != nil {
		b, _ := json.Marshal(map[string]interface{}{
			"error": err.Error(),
		})
		return string(b)
	}
	url := data["url"].(string)

	hostRet := callHost(url)
	log(hostRet)

	ret, _ := json.MarshalIndent(data, "", "  ")
	return string(ret)
}

func main() {}