Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
10 changes: 10 additions & 0 deletions component/cm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Vendored version of `go.bytecodealliance.org/cm` and published under `go.wasmcloud.dev/component/cm`.

This package is included in bindings generated by `go.wasmcloud.dev/component/wit-bindgen`.

Do not use it if you generate bindings with the upstream `go.bytecodealliance.org/cmd/wit-bindgen-go`.

## Differences

- codegen & cm packages locked to wasmcloud component-sdk version
- JSON marshaling for Option types
142 changes: 142 additions & 0 deletions component/cm/abi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package cm

import "unsafe"

// AnyInteger is a type constraint for any integer type.
type AnyInteger interface {
~int | ~uint | ~uintptr | ~int8 | ~uint8 | ~int16 | ~uint16 | ~int32 | ~uint32 | ~int64 | ~uint64
}

// Reinterpret reinterprets the bits of type From into type T.
// Will panic if the size of From is smaller than the size of To.
func Reinterpret[T, From any](from From) (to T) {
if unsafe.Sizeof(to) > unsafe.Sizeof(from) {
panic("reinterpret: size of to > from")
}
return *(*T)(unsafe.Pointer(&from))
}

// LowerString lowers a [string] into a pair of Core WebAssembly types.
//
// [string]: https://pkg.go.dev/builtin#string
func LowerString[S ~string](s S) (*byte, uint32) {
return unsafe.StringData(string(s)), uint32(len(s))
}

// LiftString lifts Core WebAssembly types into a [string].
func LiftString[T ~string, Data unsafe.Pointer | uintptr | *uint8, Len AnyInteger](data Data, len Len) T {
return T(unsafe.String((*uint8)(unsafe.Pointer(data)), int(len)))
}

// LowerList lowers a [List] into a pair of Core WebAssembly types.
func LowerList[L AnyList[T], T any](list L) (*T, uint32) {
l := (*List[T])(unsafe.Pointer(&list))
return l.data, uint32(l.len)
}

// LiftList lifts Core WebAssembly types into a [List].
func LiftList[L AnyList[T], T any, Data unsafe.Pointer | uintptr | *T, Len AnyInteger](data Data, len Len) L {
return L(NewList((*T)(unsafe.Pointer(data)), len))
}

// BoolToU32 converts a value whose underlying type is [bool] into a [uint32].
// Used to lower a [bool] into a Core WebAssembly i32 as specified in the [Canonical ABI].
//
// [bool]: https://pkg.go.dev/builtin#bool
// [uint32]: https://pkg.go.dev/builtin#uint32
// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md
func BoolToU32[B ~bool](v B) uint32 { return uint32(*(*uint8)(unsafe.Pointer(&v))) }

// U32ToBool converts a [uint32] into a [bool].
// Used to lift a Core WebAssembly i32 into a [bool] as specified in the [Canonical ABI].
//
// [uint32]: https://pkg.go.dev/builtin#uint32
// [bool]: https://pkg.go.dev/builtin#bool
// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md
func U32ToBool(v uint32) bool { tmp := uint8(v); return *(*bool)(unsafe.Pointer(&tmp)) }

// F32ToU32 maps the bits of a [float32] into a [uint32].
// Used to lower a [float32] into a Core WebAssembly i32 as specified in the [Canonical ABI].
//
// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md
// [float32]: https://pkg.go.dev/builtin#float32
// [uint32]: https://pkg.go.dev/builtin#uint32
func F32ToU32(v float32) uint32 { return *(*uint32)(unsafe.Pointer(&v)) }

// U32ToF32 maps the bits of a [uint32] into a [float32].
// Used to lift a Core WebAssembly i32 into a [float32] as specified in the [Canonical ABI].
//
// [uint32]: https://pkg.go.dev/builtin#uint32
// [float32]: https://pkg.go.dev/builtin#float32
// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md
func U32ToF32(v uint32) float32 { return *(*float32)(unsafe.Pointer(&v)) }

// F64ToU64 maps the bits of a [float64] into a [uint64].
// Used to lower a [float64] into a Core WebAssembly i64 as specified in the [Canonical ABI].
//
// [float64]: https://pkg.go.dev/builtin#float64
// [uint64]: https://pkg.go.dev/builtin#uint64
// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md
//
// [uint32]: https://pkg.go.dev/builtin#uint32
func F64ToU64(v float64) uint64 { return *(*uint64)(unsafe.Pointer(&v)) }

// U64ToF64 maps the bits of a [uint64] into a [float64].
// Used to lift a Core WebAssembly i64 into a [float64] as specified in the [Canonical ABI].
//
// [uint64]: https://pkg.go.dev/builtin#uint64
// [float64]: https://pkg.go.dev/builtin#float64
// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md
func U64ToF64(v uint64) float64 { return *(*float64)(unsafe.Pointer(&v)) }

// F32ToU64 maps the bits of a [float32] into a [uint64].
// Used to lower a [float32] into a Core WebAssembly i64 when required by the [Canonical ABI].
//
// [float32]: https://pkg.go.dev/builtin#float32
// [uint64]: https://pkg.go.dev/builtin#uint64
// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md
func F32ToU64(v float32) uint64 { return uint64(*(*uint32)(unsafe.Pointer(&v))) }

// U64ToF32 maps the bits of a [uint64] into a [float32].
// Used to lift a Core WebAssembly i64 into a [float32] when required by the [Canonical ABI].
//
// [uint64]: https://pkg.go.dev/builtin#uint64
// [float32]: https://pkg.go.dev/builtin#float32
// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md
func U64ToF32(v uint64) float32 {
truncated := uint32(v)
return *(*float32)(unsafe.Pointer(&truncated))
}

// PointerToU32 converts a pointer of type *T into a [uint32].
// Used to lower a pointer into a Core WebAssembly i32 as specified in the [Canonical ABI].
//
// [uint32]: https://pkg.go.dev/builtin#uint32
// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md
func PointerToU32[T any](v *T) uint32 { return uint32(uintptr(unsafe.Pointer(v))) }

// U32ToPointer converts a [uint32] into a pointer of type *T.
// Used to lift a Core WebAssembly i32 into a pointer as specified in the [Canonical ABI].
//
// [uint32]: https://pkg.go.dev/builtin#uint32
// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md
func U32ToPointer[T any](v uint32) *T { return (*T)(unsafePointer(uintptr(v))) }

// PointerToU64 converts a pointer of type *T into a [uint64].
// Used to lower a pointer into a Core WebAssembly i64 as specified in the [Canonical ABI].
//
// [uint64]: https://pkg.go.dev/builtin#uint64
// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md
func PointerToU64[T any](v *T) uint64 { return uint64(uintptr(unsafe.Pointer(v))) }

// U64ToPointer converts a [uint64] into a pointer of type *T.
// Used to lift a Core WebAssembly i64 into a pointer as specified in the [Canonical ABI].
//
// [uint64]: https://pkg.go.dev/builtin#uint64
// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md
func U64ToPointer[T any](v uint64) *T { return (*T)(unsafePointer(uintptr(v))) }

// Appease vet, see https://github.com/golang/go/issues/58625
func unsafePointer(p uintptr) unsafe.Pointer {
return *(*unsafe.Pointer)(unsafe.Pointer(&p))
}
83 changes: 83 additions & 0 deletions component/cm/abi_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package cm

import (
"math"
"testing"
)

func TestIntConversions(t *testing.T) {
for i := int8(math.MinInt8); i < math.MaxInt8; i++ {
testIntRoundTrip[uint32](t, i)
testIntRoundTrip[uint64](t, i)
}

for i := uint8(0); i < math.MaxUint8; i++ {
testIntRoundTrip[uint32](t, i)
testIntRoundTrip[uint64](t, i)
}

for i := int16(math.MinInt16); i < math.MaxInt16; i++ {
testIntRoundTrip[uint32](t, i)
testIntRoundTrip[uint64](t, i)
}

for i := uint16(0); i < math.MaxUint16; i++ {
testIntRoundTrip[uint32](t, i)
testIntRoundTrip[uint64](t, i)
}

// int32/uint32 into uint32
testIntRoundTrip[uint32](t, int32(0))
testIntRoundTrip[uint32](t, int32(math.MinInt8))
testIntRoundTrip[uint32](t, int32(math.MinInt16))
testIntRoundTrip[uint32](t, int32(math.MinInt32))
testIntRoundTrip[uint32](t, int32(math.MaxInt8))
testIntRoundTrip[uint32](t, int32(math.MaxInt16))
testIntRoundTrip[uint32](t, int32(math.MaxInt32))
testIntRoundTrip[uint32](t, uint32(0))
testIntRoundTrip[uint32](t, uint32(math.MaxUint8))
testIntRoundTrip[uint32](t, uint32(math.MaxUint16))
testIntRoundTrip[uint32](t, uint32(math.MaxUint32))

// int32/uint32 into uint64
testIntRoundTrip[uint64](t, int32(0))
testIntRoundTrip[uint64](t, int32(math.MinInt8))
testIntRoundTrip[uint64](t, int32(math.MinInt16))
testIntRoundTrip[uint64](t, int32(math.MinInt32))
testIntRoundTrip[uint64](t, int32(math.MaxInt8))
testIntRoundTrip[uint64](t, int32(math.MaxInt16))
testIntRoundTrip[uint64](t, int32(math.MaxInt32))
testIntRoundTrip[uint64](t, uint32(0))
testIntRoundTrip[uint64](t, uint32(math.MaxUint8))
testIntRoundTrip[uint64](t, uint32(math.MaxUint16))
testIntRoundTrip[uint64](t, uint32(math.MaxUint32))

// int64/uint64 into uint64
testIntRoundTrip[uint64](t, int64(0))
testIntRoundTrip[uint64](t, int64(math.MinInt8))
testIntRoundTrip[uint64](t, int64(math.MinInt16))
testIntRoundTrip[uint64](t, int64(math.MinInt32))
testIntRoundTrip[uint64](t, int64(math.MaxInt8))
testIntRoundTrip[uint64](t, int64(math.MaxInt16))
testIntRoundTrip[uint64](t, int64(math.MaxInt32))
testIntRoundTrip[uint64](t, uint64(0))
testIntRoundTrip[uint64](t, uint64(math.MaxUint8))
testIntRoundTrip[uint64](t, uint64(math.MaxUint16))
testIntRoundTrip[uint64](t, uint64(math.MaxUint32))
}

func testIntRoundTrip[Core CoreIntegers, From Integers](t *testing.T, want From) {
core := Core(want) // Convert to a core integer type
got := From(core) // Convert back to original type
if got != want {
t.Errorf("testLowerLift[%T, %T](t, %v): got %v, expected %v", want, core, want, got, want)
}
}

type Integers interface {
int8 | uint8 | int16 | uint16 | int32 | uint32 | int64 | uint64 | uintptr
}

type CoreIntegers interface {
uint32 | uint64
}
51 changes: 51 additions & 0 deletions component/cm/case.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package cm

// CaseUnmarshaler returns an function that can unmarshal text into
// [variant] or [enum] case T.
//
// [enum]: https://component-model.bytecodealliance.org/design/wit.html#enums
// [variant]: https://component-model.bytecodealliance.org/design/wit.html#variants
func CaseUnmarshaler[T ~uint8 | ~uint16 | ~uint32](cases []string) func(v *T, text []byte) error {
if len(cases) <= linearScanThreshold {
return func(v *T, text []byte) error {
if len(text) == 0 {
return &emptyTextError{}
}
s := string(text)
for i := 0; i < len(cases); i++ {
if cases[i] == s {
*v = T(i)
return nil
}
}
return &noMatchingCaseError{}
}
}

m := make(map[string]T, len(cases))
for i, v := range cases {
m[v] = T(i)
}

return func(v *T, text []byte) error {
if len(text) == 0 {
return &emptyTextError{}
}
c, ok := m[string(text)]
if !ok {
return &noMatchingCaseError{}
}
*v = c
return nil
}
}

const linearScanThreshold = 16

type emptyTextError struct{}

func (*emptyTextError) Error() string { return "empty text" }

type noMatchingCaseError struct{}

func (*noMatchingCaseError) Error() string { return "no matching case" }
34 changes: 34 additions & 0 deletions component/cm/case_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package cm

import (
"strings"
"testing"
)

func TestCaseUnmarshaler(t *testing.T) {
tests := []struct {
name string
cases []string
}{
{"nil", nil},
{"empty slice", []string{}},
{"a b c", strings.SplitAfter("abc", "")},
{"a b c d e f g", strings.SplitAfter("abcdefg", "")},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
f := CaseUnmarshaler[uint8](tt.cases)
for want, c := range tt.cases {
var got uint8
err := f(&got, []byte(c))
if err != nil {
t.Error(err)
return
}
if got != uint8(want) {
t.Errorf("f(%q): got %d, expected %d", c, got, want)
}
}
})
}
}
61 changes: 61 additions & 0 deletions component/cm/debug_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package cm

import (
"reflect"
"strings"
"unsafe"
)

func typeName(v any) string {
var name string
if t := reflect.TypeOf(v); t.Kind() == reflect.Ptr {
name = "*" + t.Elem().String()
} else {
name = t.String()
}
return strings.ReplaceAll(name, " ", "")
}

func sizePlusAlignOf[T any]() uintptr {
var v T
return unsafe.Sizeof(v) + unsafe.Alignof(v)
}

func alignOf[T any]() uintptr {
var v T
return unsafe.Alignof(v)
}

func zeroPtr[T any]() *T {
var zero T
return &zero
}

// TODO: remove this when TinyGo supports unsafe.Offsetof
func offsetOf[Struct any, Field any](s *Struct, f *Field) uintptr {
return uintptr(unsafe.Pointer(f)) - uintptr(unsafe.Pointer(s))
}

// VariantDebug is an interface used in tests to validate layout of variant types.
type VariantDebug interface {
Size() uintptr
DataAlign() uintptr
DataOffset() uintptr
}

func (v variant[Disc, Shape, Align]) Size() uintptr { return unsafe.Sizeof(v) }
func (v variant[Disc, Shape, Align]) DataAlign() uintptr { return unsafe.Alignof(v.data) }
func (v variant[Disc, Shape, Align]) DataOffset() uintptr { return offsetOf(&v, &v.data) }

// ResultDebug is an interface used in tests to validate layout of result types.
type ResultDebug interface {
VariantDebug
}

func (r BoolResult) Size() uintptr { return unsafe.Sizeof(r) }
func (r BoolResult) DataAlign() uintptr { return 0 }
func (r BoolResult) DataOffset() uintptr { return 0 }

func (r result[Shape, OK, Err]) Size() uintptr { return unsafe.Sizeof(r) }
func (r result[Shape, OK, Err]) DataAlign() uintptr { return unsafe.Alignof(r.data) }
func (r result[Shape, OK, Err]) DataOffset() uintptr { return offsetOf(&r, &r.data) }
Loading
Loading