github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/examples/allocation/tinygo/greet.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	_ "embed"
     6  	"fmt"
     7  	"log"
     8  	"os"
     9  
    10  	wazero "github.com/wasilibs/wazerox"
    11  	"github.com/wasilibs/wazerox/api"
    12  	"github.com/wasilibs/wazerox/imports/wasi_snapshot_preview1"
    13  )
    14  
    15  // greetWasm was compiled using `tinygo build -o greet.wasm -scheduler=none --no-debug -target=wasi greet.go`
    16  //
    17  //go:embed testdata/greet.wasm
    18  var greetWasm []byte
    19  
    20  // main shows how to interact with a WebAssembly function that was compiled
    21  // from TinyGo.
    22  //
    23  // See README.md for a full description.
    24  func main() {
    25  	// Choose the context to use for function calls.
    26  	ctx := context.Background()
    27  
    28  	// Create a new WebAssembly Runtime.
    29  	r := wazero.NewRuntime(ctx)
    30  	defer r.Close(ctx) // This closes everything this Runtime created.
    31  
    32  	// Instantiate a Go-defined module named "env" that exports a function to
    33  	// log to the console.
    34  	_, err := r.NewHostModuleBuilder("env").
    35  		NewFunctionBuilder().WithFunc(logString).Export("log").
    36  		Instantiate(ctx)
    37  	if err != nil {
    38  		log.Panicln(err)
    39  	}
    40  
    41  	// Note: testdata/greet.go doesn't use WASI, but TinyGo needs it to
    42  	// implement functions such as panic.
    43  	wasi_snapshot_preview1.MustInstantiate(ctx, r)
    44  
    45  	// Instantiate a WebAssembly module that imports the "log" function defined
    46  	// in "env" and exports "memory" and functions we'll use in this example.
    47  	mod, err := r.Instantiate(ctx, greetWasm)
    48  	if err != nil {
    49  		log.Panicln(err)
    50  	}
    51  
    52  	// Get references to WebAssembly functions we'll use in this example.
    53  	greet := mod.ExportedFunction("greet")
    54  	greeting := mod.ExportedFunction("greeting")
    55  	// These are undocumented, but exported. See tinygo-org/tinygo#2788
    56  	malloc := mod.ExportedFunction("malloc")
    57  	free := mod.ExportedFunction("free")
    58  
    59  	// Let's use the argument to this main function in Wasm.
    60  	name := os.Args[1]
    61  	nameSize := uint64(len(name))
    62  
    63  	// Instead of an arbitrary memory offset, use TinyGo's allocator. Notice
    64  	// there is nothing string-specific in this allocation function. The same
    65  	// function could be used to pass binary serialized data to Wasm.
    66  	results, err := malloc.Call(ctx, nameSize)
    67  	if err != nil {
    68  		log.Panicln(err)
    69  	}
    70  	namePtr := results[0]
    71  	// This pointer is managed by TinyGo, but TinyGo is unaware of external usage.
    72  	// So, we have to free it when finished
    73  	defer free.Call(ctx, namePtr)
    74  
    75  	// The pointer is a linear memory offset, which is where we write the name.
    76  	if !mod.Memory().Write(uint32(namePtr), []byte(name)) {
    77  		log.Panicf("Memory.Write(%d, %d) out of range of memory size %d",
    78  			namePtr, nameSize, mod.Memory().Size())
    79  	}
    80  
    81  	// Now, we can call "greet", which reads the string we wrote to memory!
    82  	_, err = greet.Call(ctx, namePtr, nameSize)
    83  	if err != nil {
    84  		log.Panicln(err)
    85  	}
    86  
    87  	// Finally, we get the greeting message "greet" printed. This shows how to
    88  	// read-back something allocated by TinyGo.
    89  	ptrSize, err := greeting.Call(ctx, namePtr, nameSize)
    90  	if err != nil {
    91  		log.Panicln(err)
    92  	}
    93  
    94  	greetingPtr := uint32(ptrSize[0] >> 32)
    95  	greetingSize := uint32(ptrSize[0])
    96  
    97  	// This pointer is managed by TinyGo, but TinyGo is unaware of external usage.
    98  	// So, we have to free it when finished
    99  	if greetingPtr != 0 {
   100  		defer func() {
   101  			_, err := free.Call(ctx, uint64(greetingPtr))
   102  			if err != nil {
   103  				log.Panicln(err)
   104  			}
   105  		}()
   106  	}
   107  
   108  	// The pointer is a linear memory offset, which is where we write the name.
   109  	if bytes, ok := mod.Memory().Read(greetingPtr, greetingSize); !ok {
   110  		log.Panicf("Memory.Read(%d, %d) out of range of memory size %d",
   111  			greetingPtr, greetingSize, mod.Memory().Size())
   112  	} else {
   113  		fmt.Println("go >>", string(bytes))
   114  	}
   115  }
   116  
   117  func logString(_ context.Context, m api.Module, offset, byteCount uint32) {
   118  	buf, ok := m.Memory().Read(offset, byteCount)
   119  	if !ok {
   120  		log.Panicf("Memory.Read(%d, %d) out of range", offset, byteCount)
   121  	}
   122  	fmt.Println(string(buf))
   123  }