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:
- Strings are passed as
(pointer, length)pairs and returned as a packeduint64(high 32 bits = pointer, low 32 bits = length). - Float64 arrays are written as contiguous bytes in memory.
- Host function callbacks use the same packed
uint64protocol.
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
callHostwrapper is required to pass the host function tohost.CallString().
Building a WASM Guest Module
Compile with TinyGo targeting WASI:
tinygo build -o main.wasm -target=wasi main.go
Requirements:
- TinyGo installed
- Go 1.24+
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