diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 0000000..c116233 --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,25 @@ +name: Checks + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + contents: read + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: extractions/setup-just@v3 + - uses: actions/setup-go@v5 + with: + go-version: "1.25" + - uses: golangci/golangci-lint-action@v6 + with: + version: latest + install-only: true + - run: just lint diff --git a/.gitignore b/.gitignore index 6985cf1..aaadf73 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,32 @@ -# Generated by Cargo -# will have compiled files and executables -debug/ -target/ +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock +# Test binary, built with `go test -c` +*.test -# These are backup files generated by rustfmt -**/*.rs.bk +# Code coverage profiles and other test artifacts +*.out +coverage.* +*.coverprofile +profile.cov -# MSVC Windows builds of rustc generate these, which store debugging information -*.pdb +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work +go.work.sum + +# env file +.env + +# Editor/IDE +# .idea/ +# .vscode/ diff --git a/Justfile b/Justfile new file mode 100644 index 0000000..9f3b205 --- /dev/null +++ b/Justfile @@ -0,0 +1,9 @@ +[doc('Show available commands')] +@default: + just --list + +[doc('Run linter')] +@lint: + echo "Lint..." + command -v golangci-lint >/dev/null 2>&1 || { echo "golangci-lint is not installed or in PATH"; exit 1; } + GOARCH=wasm GOOS=wasip1 golangci-lint run diff --git a/README.md b/README.md index 5c29e9c..8028582 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,11 @@
-

ba-template

+

go-pkg

+

+ Golang packages for the Bytecode Alliance componentize-go project +

A Bytecode Alliance project

- build status + build status zulip chat

@@ -10,9 +13,9 @@

-# ba-template +# Overview -Starter template for Bytecode Alliance repositories. +This is a set of Golang packages for the Bytecode Alliance componentize-go project. ## Questions? diff --git a/docs/404.html b/docs/404.html new file mode 100644 index 0000000..3da2bcd --- /dev/null +++ b/docs/404.html @@ -0,0 +1,11 @@ + + + go.bytecodealliance.org + + + + +

Not Found

+

Looking for Go packages? Try pkg.go.dev

+ + diff --git a/docs/CNAME b/docs/CNAME new file mode 100644 index 0000000..a060d84 --- /dev/null +++ b/docs/CNAME @@ -0,0 +1 @@ +go.bytecodealliance.org diff --git a/docs/cm.html b/docs/cm.html new file mode 100644 index 0000000..5140d6a --- /dev/null +++ b/docs/cm.html @@ -0,0 +1,11 @@ + + + go.bytecodealliance.org/cm + + + + + + Redirecting to documentation… + + diff --git a/docs/cmd.html b/docs/cmd.html new file mode 100644 index 0000000..9adf5b3 --- /dev/null +++ b/docs/cmd.html @@ -0,0 +1,11 @@ + + + go.bytecodealliance.org/cmd + + + + + + Redirecting to documentation… + + diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..cebd2e7 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,10 @@ + + + go.bytecodealliance.org + + + + + Redirecting to documentation… + + diff --git a/docs/wit.html b/docs/wit.html new file mode 100644 index 0000000..38b5ed3 --- /dev/null +++ b/docs/wit.html @@ -0,0 +1,11 @@ + + + go.bytecodealliance.org/wit + + + + + + Redirecting to documentation… + + diff --git a/docs/x.html b/docs/x.html new file mode 100644 index 0000000..3fbbd1d --- /dev/null +++ b/docs/x.html @@ -0,0 +1,11 @@ + + + go.bytecodealliance.org/x + + + + + + Redirecting to documentation… + + diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..141681c --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module go.bytecodealliance.org + +go 1.25 diff --git a/pkg/wit/async/async.go b/pkg/wit/async/async.go new file mode 100644 index 0000000..710fd37 --- /dev/null +++ b/pkg/wit/async/async.go @@ -0,0 +1,239 @@ +package async + +import ( + "fmt" + "runtime" + "unsafe" + + witRuntime "go.bytecodealliance.org/pkg/wit/runtime" +) + +// Note: These constants have specific values defined by the canonical ABI, so we use explicit values instead of iota. + +const ( + EVENT_NONE uint32 = 0 + EVENT_SUBTASK uint32 = 1 + EVENT_STREAM_READ uint32 = 2 + EVENT_STREAM_WRITE uint32 = 3 + EVENT_FUTURE_READ uint32 = 4 + EVENT_FUTURE_WRITE uint32 = 5 +) + +const ( + STATUS_STARTING uint32 = 0 + STATUS_STARTED uint32 = 1 + STATUS_RETURNED uint32 = 2 +) + +const ( + CALLBACK_CODE_EXIT uint32 = 0 + CALLBACK_CODE_YIELD uint32 = 1 + CALLBACK_CODE_WAIT uint32 = 2 +) + +const ( + RETURN_CODE_BLOCKED uint32 = 0xFFFFFFFF + RETURN_CODE_COMPLETED uint32 = 0 + RETURN_CODE_DROPPED uint32 = 1 +) + +type unit struct{} + +type taskState struct { + channel chan unit + waitableSet uint32 + pending map[uint32]chan uint32 + yielding chan unit + pinner runtime.Pinner +} + +var state *taskState = nil + +func Run(closure func()) uint32 { + state = &taskState{ + make(chan unit), + 0, + make(map[uint32]chan uint32), + nil, + runtime.Pinner{}, + } + state.pinner.Pin(state) + + defer func() { + state = nil + }() + + go closure() + + return callback(EVENT_NONE, 0, 0) +} + +func Callback(event0, event1, event2 uint32) uint32 { + state = (*taskState)(contextGet()) + contextSet(nil) + + return callback(event0, event1, event2) +} + +//go:linkname wasiOnIdle runtime.wasiOnIdle +func wasiOnIdle(callback func() bool) + +func callback(event0, event1, event2 uint32) uint32 { + yielding := state.yielding + if state.yielding != nil { + state.yielding = nil + yielding <- unit{} + } + + // Tell the Go scheduler to write to `state.channel` only after all + // goroutines have either blocked or exited. This allows us to reliably + // delay returning control to the host until there's truly nothing more + // we can do in the guest. + // + // Note that this function is _not_ currently part of upstream Go; it + // requires [this + // patch](https://github.com/dicej/go/commit/40fc123d5bce6448fc4e4601fd33bad4250b36a5) + wasiOnIdle(func() bool { + state.channel <- unit{} + return true + }) + defer wasiOnIdle(func() bool { + return false + }) + + for { + switch event0 { + case EVENT_NONE: + + case EVENT_SUBTASK: + switch event2 { + case STATUS_STARTING: + panic(fmt.Sprintf("unexpected subtask status: %v", event2)) + + case STATUS_STARTED: + + case STATUS_RETURNED: + waitableJoin(event1, 0) + subtaskDrop(event1) + channel := state.pending[event1] + delete(state.pending, event1) + channel <- event2 + + default: + panic("todo") + } + + case EVENT_STREAM_READ, EVENT_STREAM_WRITE, EVENT_FUTURE_READ, EVENT_FUTURE_WRITE: + waitableJoin(event1, 0) + channel := state.pending[event1] + delete(state.pending, event1) + channel <- event2 + + default: + panic("todo") + } + + // Block this goroutine until the scheduler wakes us up. + (<-state.channel) + + if state.yielding != nil { + contextSet(unsafe.Pointer(state)) + if len(state.pending) == 0 { + return CALLBACK_CODE_YIELD + } else { + if state.waitableSet == 0 { + panic("unreachable") + } + event0, event1, event2 = func() (uint32, uint32, uint32) { + pinner := runtime.Pinner{} + defer pinner.Unpin() + buffer := witRuntime.Allocate(&pinner, 8, 4) + event0 := waitableSetPoll(state.waitableSet, buffer) + return event0, + unsafe.Slice((*uint32)(buffer), 2)[0], + unsafe.Slice((*uint32)(buffer), 2)[1] + }() + if event0 == EVENT_NONE { + return CALLBACK_CODE_YIELD + } + } + } else if len(state.pending) == 0 { + state.pinner.Unpin() + if state.waitableSet != 0 { + waitableSetDrop(state.waitableSet) + } + return CALLBACK_CODE_EXIT + } else { + if state.waitableSet == 0 { + panic("unreachable") + } + contextSet(unsafe.Pointer(state)) + return CALLBACK_CODE_WAIT | (state.waitableSet << 4) + } + } +} + +func SubtaskWait(status uint32) { + subtask := status >> 4 + status = status & 0xF + + switch status { + case STATUS_STARTING, STATUS_STARTED: + if state.waitableSet == 0 { + state.waitableSet = waitableSetNew() + } + waitableJoin(subtask, state.waitableSet) + channel := make(chan uint32) + state.pending[subtask] = channel + (<-channel) + + case STATUS_RETURNED: + + default: + panic(fmt.Sprintf("unexpected subtask status: %v", status)) + } +} + +func FutureOrStreamWait(code uint32, handle int32) (uint32, uint32) { + if code == RETURN_CODE_BLOCKED { + if state.waitableSet == 0 { + state.waitableSet = waitableSetNew() + } + waitableJoin(uint32(handle), state.waitableSet) + channel := make(chan uint32) + state.pending[uint32(handle)] = channel + code = (<-channel) + } + + count := code >> 4 + code = code & 0xF + + return code, count +} + +func Yield() { + channel := make(chan unit) + state.yielding = channel + (<-channel) +} + +//go:wasmimport $root [waitable-set-new] +func waitableSetNew() uint32 + +//go:wasmimport $root [waitable-set-poll] +func waitableSetPoll(waitableSet uint32, eventPayload unsafe.Pointer) uint32 + +//go:wasmimport $root [waitable-set-drop] +func waitableSetDrop(waitableSet uint32) + +//go:wasmimport $root [waitable-join] +func waitableJoin(waitable, waitableSet uint32) + +//go:wasmimport $root [context-get-0] +func contextGet() unsafe.Pointer + +//go:wasmimport $root [context-set-0] +func contextSet(value unsafe.Pointer) + +//go:wasmimport $root [subtask-drop] +func subtaskDrop(subtask uint32) diff --git a/pkg/wit/runtime/runtime.go b/pkg/wit/runtime/runtime.go new file mode 100644 index 0000000..105ba13 --- /dev/null +++ b/pkg/wit/runtime/runtime.go @@ -0,0 +1,123 @@ +package runtime + +import ( + "fmt" + "runtime" + "unsafe" +) + +type Handle struct { + value int32 +} + +func (h *Handle) Use() int32 { + if h.value == 0 { + panic("nil handle") + } + return h.value +} + +func (h *Handle) Take() int32 { + if h.value == 0 { + panic("nil handle") + } + value := h.value + h.value = 0 + return value +} + +func (h *Handle) Set(value int32) { + if value == 0 { + panic("nil handle") + } + if h.value != 0 { + panic("handle already set") + } + h.value = value +} + +func (h *Handle) TakeOrNil() int32 { + value := h.value + h.value = 0 + return value +} + +func MakeHandle(value int32) *Handle { + if value == 0 { + panic("nil handle") + } + return &Handle{value} +} + +func Allocate(pinner *runtime.Pinner, size, align uintptr) unsafe.Pointer { + pointer := allocateRaw(size, align) + pinner.Pin(pointer) + return pointer +} + +func allocateRaw(size, align uintptr) unsafe.Pointer { + if size == 0 { + return nil + } + + if size%align != 0 { + panic(fmt.Sprintf("size %v is not compatible with alignment %v", size, align)) + } + + switch align { + case 1: + return unsafe.Pointer(unsafe.SliceData(make([]uint8, size))) + case 2: + return unsafe.Pointer(unsafe.SliceData(make([]uint16, size/align))) + case 4: + return unsafe.Pointer(unsafe.SliceData(make([]uint32, size/align))) + case 8: + return unsafe.Pointer(unsafe.SliceData(make([]uint64, size/align))) + default: + panic(fmt.Sprintf("unsupported alignment: %v", align)) + } +} + +// NB: `cabi_realloc` may be called before the Go runtime has been initialized, +// in which case we need to use `runtime.sbrk` to do allocations. The following +// is an abbreviation of [Till's +// efforts](https://github.com/bytecodealliance/go-modules/pull/367). + +//go:linkname sbrk runtime.sbrk +func sbrk(n uintptr) unsafe.Pointer + +//nolint:unused +var useGCAllocations = false + +func init() { + useGCAllocations = true +} + +//nolint:unused +func offset(ptr, align uintptr) uintptr { + newptr := (ptr + align - 1) &^ (align - 1) + return newptr - ptr +} + +var pinner = runtime.Pinner{} + +func Unpin() { + pinner.Unpin() +} + +//nolint:unused +//go:wasmexport cabi_realloc +func cabiRealloc(oldPointer unsafe.Pointer, oldSize, align, newSize uintptr) unsafe.Pointer { + if oldPointer != nil || oldSize != 0 { + panic("todo") + } + + if useGCAllocations { + return Allocate(&pinner, newSize, align) + } else { + alignedSize := newSize + offset(newSize, align) + unaligned := sbrk(alignedSize) + off := offset(uintptr(unaligned), align) + return unsafe.Add(unaligned, off) + } +} diff --git a/pkg/wit/types/future.go b/pkg/wit/types/future.go new file mode 100644 index 0000000..79c7cbb --- /dev/null +++ b/pkg/wit/types/future.go @@ -0,0 +1,151 @@ +package wit_types + +import ( + "runtime" + "unsafe" + + witAsync "go.bytecodealliance.org/pkg/wit/async" + witRuntime "go.bytecodealliance.org/pkg/wit/runtime" +) + +type FutureVtable[T any] struct { + Size uint32 + Align uint32 + Read func(handle int32, item unsafe.Pointer) uint32 + Write func(handle int32, item unsafe.Pointer) uint32 + CancelRead func(handle int32) uint32 + CancelWrite func(handle int32) uint32 + DropReadable func(handle int32) + DropWritable func(handle int32) + Lift func(src unsafe.Pointer) T + Lower func(pinner *runtime.Pinner, value T, dst unsafe.Pointer) func() +} + +type FutureReader[T any] struct { + vtable *FutureVtable[T] + handle *witRuntime.Handle +} + +// Blocks until the future completes and returns its value. +// +// # Panic +// +// Read will panic if multiple concurrent or sequential reads are attempted on the same future. +func (self *FutureReader[T]) Read() T { + handle := self.handle.Take() + defer self.vtable.DropReadable(handle) + + pinner := runtime.Pinner{} + defer pinner.Unpin() + + buffer := witRuntime.Allocate(&pinner, uintptr(self.vtable.Size), uintptr(self.vtable.Align)) + + code, _ := witAsync.FutureOrStreamWait(self.vtable.Read(handle, buffer), handle) + + switch code { + case witAsync.RETURN_CODE_COMPLETED: + if self.vtable.Lift == nil { + return unsafe.Slice((*T)(buffer), 1)[0] + } else { + return self.vtable.Lift(buffer) + } + + case witAsync.RETURN_CODE_DROPPED: + panic("unreachable") + + default: + panic("todo: handle cancellation") + } +} + +// Notify the host that the FutureReader is no longer being used. +func (self *FutureReader[T]) Drop() { + handle := self.handle.TakeOrNil() + if handle != 0 { + self.vtable.DropReadable(handle) + } +} + +func (self *FutureReader[T]) TakeHandle() int32 { + return self.handle.Take() +} + +func (self *FutureReader[T]) SetHandle(handle int32) { + self.handle.Set(handle) +} + +func MakeFutureReader[T any](vtable *FutureVtable[T], handleValue int32) *FutureReader[T] { + handle := witRuntime.MakeHandle(handleValue) + value := &FutureReader[T]{vtable, handle} + runtime.AddCleanup(value, func(_ int) { + handleValue := handle.TakeOrNil() + if handleValue != 0 { + vtable.DropReadable(handleValue) + } + }, 0) + return value +} + +type FutureWriter[T any] struct { + vtable *FutureVtable[T] + handle *witRuntime.Handle +} + +// Writes data to a future. +// +// # Panic +// +// Write will panic if multiple concurrent or sequential writes are attempted on the same future. +func (self *FutureWriter[T]) Write(item T) bool { + handle := self.handle.Take() + defer self.vtable.DropWritable(handle) + + pinner := runtime.Pinner{} + defer pinner.Unpin() + + var lifter func() + var buffer unsafe.Pointer + if self.vtable.Lower == nil { + buffer = unsafe.Pointer(unsafe.SliceData([]T{item})) + pinner.Pin(buffer) + } else { + buffer = witRuntime.Allocate(&pinner, uintptr(self.vtable.Size), uintptr(self.vtable.Align)) + lifter = self.vtable.Lower(&pinner, item, buffer) + } + + code, _ := witAsync.FutureOrStreamWait(self.vtable.Write(handle, buffer), handle) + + switch code { + case witAsync.RETURN_CODE_COMPLETED: + return true + + case witAsync.RETURN_CODE_DROPPED: + if lifter != nil { + lifter() + } + return false + + default: + panic("todo: handle cancellation") + } +} + +// Notify the host that the FutureWriter is no longer being used. +func (self *FutureWriter[T]) Drop() { + handle := self.handle.TakeOrNil() + if handle != 0 { + self.vtable.DropWritable(handle) + } +} + +func MakeFutureWriter[T any](vtable *FutureVtable[T], handleValue int32) *FutureWriter[T] { + handle := witRuntime.MakeHandle(handleValue) + value := &FutureWriter[T]{vtable, handle} + runtime.AddCleanup(value, func(_ int) { + handleValue := handle.TakeOrNil() + if handleValue != 0 { + vtable.DropWritable(handleValue) + } + }, 0) + return value +} diff --git a/pkg/wit/types/option.go b/pkg/wit/types/option.go new file mode 100644 index 0000000..e54d804 --- /dev/null +++ b/pkg/wit/types/option.go @@ -0,0 +1,46 @@ +package wit_types + +const ( + OptionNone = 0 + OptionSome = 1 +) + +type Option[T any] struct { + tag uint8 + value T +} + +func (self Option[T]) Tag() uint8 { + return self.tag +} + +func (self Option[T]) Some() T { + if self.tag != OptionSome { + panic("tag mismatch") + } + return self.value +} + +func (self Option[T]) SomeOr(value T) T { + if self.tag != OptionSome { + return value + } else { + return self.value + } +} + +func (self Option[T]) IsSome() bool { + return self.tag == OptionSome +} + +func (self Option[T]) IsNone() bool { + return self.tag == OptionNone +} + +func None[T any]() Option[T] { + return Option[T]{OptionNone, make([]T, 1)[0]} +} + +func Some[T any](value T) Option[T] { + return Option[T]{OptionSome, value} +} diff --git a/pkg/wit/types/result.go b/pkg/wit/types/result.go new file mode 100644 index 0000000..92d7021 --- /dev/null +++ b/pkg/wit/types/result.go @@ -0,0 +1,45 @@ +package wit_types + +const ( + ResultOk = 0 + ResultErr = 1 +) + +type Result[T any, U any] struct { + tag uint8 + value any +} + +func (self Result[T, U]) Tag() uint8 { + return self.tag +} + +func (self Result[T, U]) Ok() T { + if self.tag != ResultOk { + panic("tag mismatch") + } + return self.value.(T) +} + +func (self Result[T, U]) Err() U { + if self.tag != ResultErr { + panic("tag mismatch") + } + return self.value.(U) +} + +func (self Result[T, U]) IsErr() bool { + return self.tag == ResultErr +} + +func (self Result[T, U]) IsOk() bool { + return self.tag == ResultOk +} + +func Ok[T any, U any](value T) Result[T, U] { + return Result[T, U]{ResultOk, value} +} + +func Err[T any, U any](value U) Result[T, U] { + return Result[T, U]{ResultErr, value} +} diff --git a/pkg/wit/types/stream.go b/pkg/wit/types/stream.go new file mode 100644 index 0000000..f121820 --- /dev/null +++ b/pkg/wit/types/stream.go @@ -0,0 +1,208 @@ +package wit_types + +import ( + "runtime" + "unsafe" + + witAsync "go.bytecodealliance.org/pkg/wit/async" + witRuntime "go.bytecodealliance.org/pkg/wit/runtime" +) + +type StreamVtable[T any] struct { + Size uint32 + Align uint32 + Read func(handle int32, items unsafe.Pointer, length uint32) uint32 + Write func(handle int32, items unsafe.Pointer, length uint32) uint32 + CancelRead func(handle int32) uint32 + CancelWrite func(handle int32) uint32 + DropReadable func(handle int32) + DropWritable func(handle int32) + Lift func(src unsafe.Pointer) T + Lower func(pinner *runtime.Pinner, value T, dst unsafe.Pointer) func() +} + +type StreamReader[T any] struct { + vtable *StreamVtable[T] + handle *witRuntime.Handle + writerDropped bool +} + +func (self *StreamReader[T]) WriterDropped() bool { + return self.writerDropped +} + +// Reads data from a stream into a destination slice. +// +// Blocks until the read completes or the destination slice is full. +// +// # Panic +// +// Read will panic if: +// - dst is empty (length 0) +// - multiple concurrent reads are attempted on the same stream +func (self *StreamReader[T]) Read(dst []T) uint32 { + if len(dst) == 0 { + panic("StreamReader.Read: destination slice cannot be empty") + } + + handle := self.handle.Take() + defer self.handle.Set(handle) + + if self.writerDropped { + return 0 + } + + pinner := runtime.Pinner{} + defer pinner.Unpin() + + var buffer unsafe.Pointer + if self.vtable.Lift == nil { + buffer = unsafe.Pointer(unsafe.SliceData(dst)) + } else { + buffer = witRuntime.Allocate( + &pinner, + uintptr(self.vtable.Size*uint32(len(dst))), + uintptr(self.vtable.Align), + ) + } + pinner.Pin(buffer) + + code, count := witAsync.FutureOrStreamWait(self.vtable.Read(handle, buffer, uint32(len(dst))), handle) + + if code == witAsync.RETURN_CODE_DROPPED { + self.writerDropped = true + } + + if self.vtable.Lift != nil { + for i := 0; i < int(count); i++ { + dst[i] = self.vtable.Lift(unsafe.Add(buffer, i*int(self.vtable.Size))) + } + } + + return count +} + +// Notify the host that the StreamReader is no longer being used. +func (self *StreamReader[T]) Drop() { + handle := self.handle.TakeOrNil() + if handle != 0 { + self.vtable.DropReadable(handle) + } +} + +func (self *StreamReader[T]) TakeHandle() int32 { + return self.handle.Take() +} + +func (self *StreamReader[T]) SetHandle(handle int32) { + self.handle.Set(handle) +} + +func MakeStreamReader[T any](vtable *StreamVtable[T], handleValue int32) *StreamReader[T] { + handle := witRuntime.MakeHandle(handleValue) + value := &StreamReader[T]{vtable, handle, false} + runtime.AddCleanup(value, func(_ int) { + handleValue := handle.TakeOrNil() + if handleValue != 0 { + vtable.DropReadable(handleValue) + } + }, 0) + return value +} + +type StreamWriter[T any] struct { + vtable *StreamVtable[T] + handle *witRuntime.Handle + readerDropped bool +} + +func (self *StreamWriter[T]) ReaderDropped() bool { + return self.readerDropped +} + +// Writes items to a stream, returning the count written (may be partial). +// +// # Panic +// +// Write will panic if multiple concurrent writes are attempted on the same stream. +func (self *StreamWriter[T]) Write(items []T) uint32 { + handle := self.handle.Take() + defer self.handle.Set(handle) + + if self.readerDropped { + return 0 + } + + pinner := runtime.Pinner{} + defer pinner.Unpin() + + writeCount := uint32(len(items)) + + var lifters []func() + var buffer unsafe.Pointer + if self.vtable.Lower == nil { + buffer = unsafe.Pointer(unsafe.SliceData(items)) + pinner.Pin(buffer) + } else { + lifters = make([]func(), 0, writeCount) + buffer = witRuntime.Allocate( + &pinner, + uintptr(self.vtable.Size*writeCount), + uintptr(self.vtable.Align), + ) + for index, item := range items { + lifters = append( + lifters, + self.vtable.Lower(&pinner, item, unsafe.Add(buffer, index*int(self.vtable.Size))), + ) + } + } + + code, count := witAsync.FutureOrStreamWait(self.vtable.Write(handle, buffer, writeCount), handle) + + if lifters != nil && count < writeCount { + for _, lifter := range lifters[count:] { + lifter() + } + } + + if code == witAsync.RETURN_CODE_DROPPED { + self.readerDropped = true + } + + return count +} + +// Writes all items to the stream, looping until complete or reader drops. +// +// # Panic +// +// WriteAll will panic if multiple concurrent writes are attempted on the same stream. +func (self *StreamWriter[T]) WriteAll(items []T) uint32 { + offset := uint32(0) + count := uint32(len(items)) + for offset < count && !self.readerDropped { + offset += self.Write(items[offset:]) + } + return offset +} + +// Notify the host that the StreamWriter is no longer being used. +func (self *StreamWriter[T]) Drop() { + handle := self.handle.TakeOrNil() + if handle != 0 { + self.vtable.DropWritable(handle) + } +} + +func MakeStreamWriter[T any](vtable *StreamVtable[T], handleValue int32) *StreamWriter[T] { + handle := witRuntime.MakeHandle(handleValue) + value := &StreamWriter[T]{vtable, handle, false} + runtime.AddCleanup(value, func(_ int) { + handleValue := handle.TakeOrNil() + if handleValue != 0 { + vtable.DropWritable(handleValue) + } + }, 0) + return value +} diff --git a/pkg/wit/types/tuple.go b/pkg/wit/types/tuple.go new file mode 100644 index 0000000..2c4d114 --- /dev/null +++ b/pkg/wit/types/tuple.go @@ -0,0 +1,185 @@ +package wit_types + +type Tuple1[T0 any] struct { + F0 T0 +} + +type Tuple2[T0 any, T1 any] struct { + F0 T0 + F1 T1 +} + +type Tuple3[T0 any, T1 any, T2 any] struct { + F0 T0 + F1 T1 + F2 T2 +} + +type Tuple4[T0 any, T1 any, T2 any, T3 any] struct { + F0 T0 + F1 T1 + F2 T2 + F3 T3 +} + +type Tuple5[T0 any, T1 any, T2 any, T3 any, T4 any] struct { + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 +} + +type Tuple6[T0 any, T1 any, T2 any, T3 any, T4 any, T5 any] struct { + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 +} + +type Tuple7[T0 any, T1 any, T2 any, T3 any, T4 any, T5 any, T6 any] struct { + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 +} + +type Tuple8[T0 any, T1 any, T2 any, T3 any, T4 any, T5 any, T6 any, T7 any] struct { + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 +} + +type Tuple9[T0 any, T1 any, T2 any, T3 any, T4 any, T5 any, T6 any, T7 any, T8 any] struct { + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 + F8 T8 +} + +type Tuple10[T0 any, T1 any, T2 any, T3 any, T4 any, T5 any, T6 any, T7 any, T8 any, T9 any] struct { + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 + F8 T8 + F9 T9 +} + +type Tuple11[T0 any, T1 any, T2 any, T3 any, T4 any, T5 any, T6 any, T7 any, T8 any, T9 any, T10 any] struct { + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 + F8 T8 + F9 T9 + F10 T10 +} + +type Tuple12[T0 any, T1 any, T2 any, T3 any, T4 any, T5 any, T6 any, T7 any, T8 any, T9 any, T10 any, T11 any] struct { + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 + F8 T8 + F9 T9 + F10 T10 + F11 T11 +} + +type Tuple13[T0 any, T1 any, T2 any, T3 any, T4 any, T5 any, T6 any, T7 any, T8 any, T9 any, T10 any, T11 any, T12 any] struct { + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 + F8 T8 + F9 T9 + F10 T10 + F11 T11 + F12 T12 +} + +type Tuple14[T0 any, T1 any, T2 any, T3 any, T4 any, T5 any, T6 any, T7 any, T8 any, T9 any, T10 any, T11 any, T12 any, T13 any] struct { + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 + F8 T8 + F9 T9 + F10 T10 + F11 T11 + F12 T12 + F13 T13 +} + +type Tuple15[T0 any, T1 any, T2 any, T3 any, T4 any, T5 any, T6 any, T7 any, T8 any, T9 any, T10 any, T11 any, T12 any, T13 any, T14 any] struct { + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 + F8 T8 + F9 T9 + F10 T10 + F11 T11 + F12 T12 + F13 T13 + F14 T14 +} + +type Tuple16[T0 any, T1 any, T2 any, T3 any, T4 any, T5 any, T6 any, T7 any, T8 any, T9 any, T10 any, T11 any, T12 any, T13 any, T14 any, T15 any] struct { + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 + F8 T8 + F9 T9 + F10 T10 + F11 T11 + F12 T12 + F13 T13 + F14 T14 + F15 T15 +} diff --git a/pkg/wit/types/unit.go b/pkg/wit/types/unit.go new file mode 100644 index 0000000..26f927b --- /dev/null +++ b/pkg/wit/types/unit.go @@ -0,0 +1,3 @@ +package wit_types + +type Unit struct{}