Golo Bridge

Golo Bridge is a Go library for TinyGo that simplifies writing WASM guest modules for GoloScript. It abstracts away the pointer arithmetic and memory encoding required to exchange strings, numbers, and host function calls across the WASM boundary.

Module: codeberg.org/TypeUnsafe/golo-script/golo-bridge

Why Golo Bridge?

When GoloScript loads a WASM module, data is exchanged through linear memory using a specific encoding:

Without Golo Bridge, every TinyGo function must manually use unsafe.Pointer, unsafe.Slice, and bit-shifting. This is verbose and error-prone.

Before (manual)

package main

import "unsafe"

var resultBuffer []byte

//export hello
func hello(ptr, size uint32) uint64 {
    inputBytes := unsafe.Slice((*byte)(unsafe.Pointer(uintptr(ptr))), size)
    name := string(inputBytes)

    result := "Hello, " + name + "!!!"

    resultBuffer = []byte(result)
    resultPtr := uint32(uintptr(unsafe.Pointer(&resultBuffer[0])))
    resultLen := uint32(len(resultBuffer))

    return (uint64(resultPtr) << 32) | uint64(resultLen)
}

func main() {}

After (with Golo Bridge)

package main

import "codeberg.org/TypeUnsafe/golo-script/golo-bridge/mem"

//export hello
func hello(ptr, size uint32) uint64 {
    name := mem.ReadString(ptr, size)
    return mem.WriteString("Hello, " + name + "!!!")
}

func main() {}

Same behavior, a fraction of the code.

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         GoloScript (Host)            β”‚
β”‚  Golo language interpreter (Go)      β”‚
β”‚  WASM runtime: wazero                β”‚
β”‚                                      β”‚
β”‚  wasmLoad() / wasmCallString() /     β”‚
β”‚  wasmCallNumbers() /                 β”‚
β”‚  wasmRegisterStringHandler()         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚  WASM function calls
               β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚      WASM Guest Module (TinyGo)      β”‚
β”‚  Uses golo-bridge/mem and            β”‚
β”‚  golo-bridge/host packages           β”‚
β”‚  Exports functions to GoloScript     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚  reads/writes
               β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚       WASM Linear Memory             β”‚
β”‚  Shared between host and guest       β”‚
β”‚  Strings encoded as ptr + length     β”‚
β”‚  Numbers as contiguous float64       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Installation

From a released version

go get codeberg.org/TypeUnsafe/golo-script/golo-bridge@v0.0.0

For local development (within the golo-script repo)

In your guest module’s go.mod:

module guest

go 1.24.0

require codeberg.org/TypeUnsafe/golo-script/golo-bridge v0.0.0

replace codeberg.org/TypeUnsafe/golo-script/golo-bridge => ../../../golo-bridge

The replace directive points to the local golo-bridge/ directory in the golo-script repository.

API Reference

Package mem

import "codeberg.org/TypeUnsafe/golo-script/golo-bridge/mem"
Function Signature Description
ReadString (ptr, size uint32) string Read a string from WASM memory.
WriteString (s string) uint64 Store a string and return a packed uint64 (ptr/length) for the host to decode.
ReadFloat64Array (ptr, length uint32) []float64 Read a []float64 from WASM memory. length is the number of elements, not bytes.
Pack (ptr, length uint32) uint64 Encode a pointer and length into a single uint64.
Unpack (packed uint64) (uint32, uint32) Decode a packed uint64 back into pointer and length.

Package host

import "codeberg.org/TypeUnsafe/golo-script/golo-bridge/host"
Function Signature Description
CallString (hostFn func(ptr, length uint32) uint64, input string) string Call a host function with a string input and decode the string result. Handles all encoding/decoding automatically.

GoloScript WASM Functions

These built-in functions are available in any .golo script:

Function Description
wasmLoad(path) Load a WASM module from a file. Returns a runner.
wasmHasFunction(runner, name) Check if the module exports a function.
wasmCallString(runner, name, input) Call an exported function with a string argument. Returns a string.
wasmCallNumbers(runner, name, array) Call an exported function with a number array. Returns a float64.
wasmRegisterStringHandler(runner, handler) Register a Golo function as a host callback for the WASM module.
wasmClose(runner) Close the runner and free resources.

Examples

1. String functions

TinyGo guest (guest/main.go):

package main

import "codeberg.org/TypeUnsafe/golo-script/golo-bridge/mem"

//export hello
func hello(ptr, size uint32) uint64 {
    name := mem.ReadString(ptr, size)
    return mem.WriteString("Hello, " + name + "!!!")
}

func main() {}

GoloScript host (main.golo):

module examples.wasm.StringWithGoloBridge

function main = |args| {
  let runner = wasmLoad("./guest/main.wasm")

  if wasmHasFunction(runner, "hello") {
    let names = ["Alice", "Bob", "Charlie"]

    foreach name in names {
      let result = wasmCallString(runner, "hello", name)
      println("  -> " + result)
    }
  }

  wasmClose(runner)
}

2. Numeric functions

TinyGo guest (guest/main.go):

package main

import "codeberg.org/TypeUnsafe/golo-script/golo-bridge/mem"

//export sum
func sum(ptr uint32, length uint32) float64 {
    numbers := mem.ReadFloat64Array(ptr, length)

    var total float64
    for _, num := range numbers {
        total += num
    }
    return total
}

//export multiply
func multiply(ptr uint32, length uint32) float64 {
    numbers := mem.ReadFloat64Array(ptr, length)

    if length == 0 {
        return 0
    }

    result := float64(1)
    for _, num := range numbers {
        result *= num
    }
    return result
}

func main() {}

GoloScript host (main.golo):

module examples.wasm.NumbersWithGoloBridge

function main = |args| {
  let runner = wasmLoad("./guest/main.wasm")

  let result1 = wasmCallNumbers(runner, "sum", [10, 20, 30, 40, 50])
  println("sum = " + result1)

  let result2 = wasmCallNumbers(runner, "multiply", [2, 3, 4, 5])
  println("multiply = " + result2)

  wasmClose(runner)
}

3. Host function callbacks

A WASM guest can call back into GoloScript through host functions. This enables bidirectional communication.

TinyGo guest (guest/main.go):

package main

import (
    "codeberg.org/TypeUnsafe/golo-script/golo-bridge/host"
    "codeberg.org/TypeUnsafe/golo-script/golo-bridge/mem"
)

// Declare the host function (provided by GoloScript at runtime)
//export hostCallStringHandler
func hostCallStringHandler(ptr, length uint32) uint64

// Wrapper needed because TinyGo cannot use imported functions as values directly
func callHost(ptr, length uint32) uint64 {
    return hostCallStringHandler(ptr, length)
}

//export processWithHost
func processWithHost(ptr, length uint32) uint64 {
    input := mem.ReadString(ptr, length)
    message := "Message from WASM: " + input

    // Call the host function β€” encoding/decoding is handled by golo-bridge
    hostResult := host.CallString(callHost, message)

    return mem.WriteString("Result from host: " + hostResult)
}

func main() {}

GoloScript host (main.golo):

module examples.wasm.HostFunctionWithGoloBridge

function main = |args| {
  let runner = wasmLoad("./guest/main.wasm")

  let myHandler = |input| {
    println("Host receives: " + input)
    let result = input: toUpperCase() + " [PROCESSED BY GOLO HOST]"
    println("Host returns: " + result)
    return result
  }

  wasmRegisterStringHandler(runner, myHandler)

  let result = wasmCallString(runner, "processWithHost", "Hello from GoloScript!")
  println("Final result: " + result)

  wasmClose(runner)
}

Note on the wrapper function: TinyGo does not allow imported/exported functions to be used directly as values. The callHost wrapper is required to pass the host function to host.CallString().

Building a WASM Guest Module

Compile with TinyGo targeting WASI:

tinygo build -o main.wasm -target=wasi main.go

Requirements:

Encoding Reference

For those who want to understand what golo-bridge abstracts:

Type Host β†’ Guest Guest β†’ Host
String input Written to memory at offset 1024. Guest receives (ptr, length). N/A
String return N/A Guest returns uint64: (ptr << 32) | length. Host reads from memory.
Float64 array Written as contiguous 8-byte values at offset 1024. Guest receives (ptr, count). Guest returns float64 directly.
Host callback Host writes result at offset 2048. Returns packed uint64. Guest calls imported function with (ptr, length).

Β© 2026 GoloScript Project | Built with Gu10berg

Subscribe: πŸ“‘ RSS | βš›οΈ Atom