Skip to content

Commit

Permalink
Merge pull request #21 from ydnar/component-model-types
Browse files Browse the repository at this point in the history
cm: Component Model types in Go
  • Loading branch information
ydnar authored Jan 14, 2024
2 parents b5b0a11 + 75b4ccd commit fd44f01
Show file tree
Hide file tree
Showing 10 changed files with 592 additions and 0 deletions.
94 changes: 94 additions & 0 deletions cm/debug_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package cm

import (
"reflect"
"strings"
"unsafe"

"github.com/ydnar/wasm-tools-go/internal/tinyunsafe"
)

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
}

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

func (v SizedVariant2[Shape, T0, T1]) Size() uintptr {
return unsafe.Sizeof(v)
}

func (v SizedVariant2[Shape, T0, T1]) ValAlign() uintptr {
return unsafe.Alignof(v.val)
}

func (v SizedVariant2[Shape, T0, T1]) ValOffset() uintptr {
return tinyunsafe.OffsetOf(&v, &v.val)
}

func (v UnsizedVariant2[T0, T1]) Size() uintptr {
return unsafe.Sizeof(v)
}

func (v UnsizedVariant2[T0, T1]) ValAlign() uintptr {
return 0
}

func (v UnsizedVariant2[T0, T1]) ValOffset() uintptr {
return 0
}

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

func (r SizedResult[S, OK, Err]) Size() uintptr {
return unsafe.Sizeof(r)
}

func (r SizedResult[S, OK, Err]) ValAlign() uintptr {
return r.v.ValAlign()
}

func (r SizedResult[S, OK, Err]) ValOffset() uintptr {
return r.v.ValOffset()
}

func (r UnsizedResult[OK, Err]) Size() uintptr {
return unsafe.Sizeof(r)
}

func (r UnsizedResult[OK, Err]) ValAlign() uintptr {
return r.v.ValAlign()
}

func (r UnsizedResult[OK, Err]) ValOffset() uintptr {
return r.v.ValOffset()
}
7 changes: 7 additions & 0 deletions cm/enum.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package cm

type (
Enum8 uint8
Enum16 uint16
Enum32 uint32
)
120 changes: 120 additions & 0 deletions cm/layout_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package cm

import (
"runtime"
"testing"
"unsafe"

"github.com/ydnar/wasm-tools-go/internal/tinyunsafe"
)

func TestFieldAlignment(t *testing.T) {
var v1 struct {
_ bool
_ [0][7]byte
u64 uint64
}
if want, got := uintptr(16), unsafe.Sizeof(v1); want != got {
t.Errorf("expected unsafe.Sizeof(v1) == %d, got %d", want, got)
}
if want, got := uintptr(8), tinyunsafe.OffsetOf(&v1, &v1.u64); want != got {
t.Errorf("expected unsafe.Offsetof(v1.u64) == %d, got %d", want, got)
}

var v2 struct {
_ bool
_ [0][7]byte
_ [0][51]float64
_ [0]struct {
uint64
_ []byte
}
u64 uint64
}
if want, got := uintptr(16), unsafe.Sizeof(v2); want != got {
t.Errorf("expected unsafe.Sizeof(v2) == %d, got %d", want, got)
}
if want, got := uintptr(8), tinyunsafe.OffsetOf(&v2, &v2.u64); want != got {
t.Errorf("expected unsafe.Offsetof(v2.u64) == %d, got %d", want, got)
}

// size 1
var v3 struct {
_ struct{}
b bool // offset 0
}
if want, got := uintptr(1), unsafe.Sizeof(v3); want != got {
t.Errorf("expected unsafe.Sizeof(v3) == %d, got %d", want, got)
}
if want, got := uintptr(0), tinyunsafe.OffsetOf(&v3, &v3.b); want != got {
t.Errorf("expected unsafe.Offsetof(v3.b) == %d, got %d", want, got)
}

// size 0
var v4 struct {
_ [0]uint32
b bool // offset 0!
}
if want, got := uintptr(4), unsafe.Sizeof(v4); want != got {
t.Errorf("expected unsafe.Sizeof(v4) == %d, got %d", want, got)
}
if want, got := uintptr(0), tinyunsafe.OffsetOf(&v4, &v4.b); want != got {
t.Errorf("expected unsafe.Offsetof(v4.b) == %d, got %d", want, got)
}
}

// TestBool verifies that Go bool size, alignment, and values are consistent
// with the Component Model Canonical ABI.
func TestBool(t *testing.T) {
var b bool
if got, want := unsafe.Sizeof(b), uintptr(1); got != want {
t.Errorf("unsafe.Sizeof(b) == %d, expected %d", got, want)
}
if got, want := unsafe.Alignof(b), uintptr(1); got != want {
t.Errorf("unsafe.Alignof(b) == %d, expected %d", got, want)
}

// uint8(false) == 0
b = false
if got, want := *(*uint8)(unsafe.Pointer(&b)), uint8(0); got != want {
t.Errorf("uint8(b) == %d, expected %d", got, want)
}

// uint8(true) == 1
b = true
if got, want := *(*uint8)(unsafe.Pointer(&b)), uint8(1); got != want {
t.Errorf("uint8(b) == %d, expected %d", got, want)
}

// low bit 1 == true
*(*uint8)(unsafe.Pointer(&b)) = 1
if got, want := b, true; got != want {
t.Errorf("b == %t, expected %t", got, want)
}

// low bit 1 == true
*(*uint8)(unsafe.Pointer(&b)) = 3
if got, want := b, true; got != want {
t.Errorf("b == %t, expected %t", got, want)
}

// low bit 1 == true
*(*uint8)(unsafe.Pointer(&b)) = 255
if got, want := b, true; got != want {
t.Errorf("b == %t, expected %t", got, want)
}

if runtime.GOARCH != "amd64" {
// low bit 0 == false
*(*uint8)(unsafe.Pointer(&b)) = 2
if got, want := b, false; got != want {
t.Errorf("b == %t, expected %t", got, want)
}

// low bit 0 == false
*(*uint8)(unsafe.Pointer(&b)) = 254
if got, want := b, false; got != want {
t.Errorf("b == %t, expected %t", got, want)
}
}
}
25 changes: 25 additions & 0 deletions cm/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package cm

import "unsafe"

// List represents a Component Model list<T>.
// The binary representation of list<T> is similar to a Go slice minus the cap field.
type List[T any] struct {
ptr *T
len uintptr
}

// ToList returns a List[T] equivalent to the Go slice s.
// The underlying slice data is not copied, and the resulting List points at the
// same array storage as the slice.
func ToList[S ~[]T, T any](s S) List[T] {
return List[T]{
ptr: unsafe.SliceData([]T(s)),
len: uintptr(len(s)),
}
}

// Slice returns a Go slice representing the List.
func (list List[T]) Slice() []T {
return unsafe.Slice(list.ptr, list.len)
}
35 changes: 35 additions & 0 deletions cm/option.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package cm

// Option represents a Component Model option<T> type.
// The first byte is a bool representing none or some,
// followed by storage for the associated type T.
type Option[T any] struct {
isSome bool
v T
}

// None returns an Option[T] representing the none value,
// equivalent to the zero value of Option[T].
func None[T any]() Option[T] {
return Option[T]{}
}

// Some returns an Option[T] representing the some value.
func Some[T any](v T) Option[T] {
return Option[T]{
isSome: true,
v: v,
}
}

// None returns true if o represents the none value.
// None returns false if o represents the some value.
func (o Option[T]) None() bool {
return !o.isSome
}

// Some returns T, true if o represents the some value.
// Some returns T, false if o represents the none value.
func (o Option[T]) Some() (T, bool) {
return o.v, o.isSome
}
95 changes: 95 additions & 0 deletions cm/result.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package cm

// Result is the common interface implemented by result types.
type Result[OK, Err any] interface {
IsErr() bool
SetOK(OK)
SetErr(Err)
OK() (ok OK, isOK bool)
Err() (err Err, isErr bool)
}

// OKSizedResult represents a result sized to hold the OK type.
// The size of the OK type must be greater than or equal to the size of the Err type.
// For results with two zero-length types, use UnsizedResult.
type OKSizedResult[OK any, Err any] struct {
SizedResult[Shape[OK], OK, Err]
}

// ErrSizedResult represents a result sized to hold the Err type.
// The size of the Err type must be greater than or equal to the size of the OK type.
// For results with two zero-length types, use UnsizedResult.
type ErrSizedResult[OK any, Err any] struct {
SizedResult[Shape[Err], OK, Err]
}

// SizedResult is a tagged union that represents either the OK type or the Err type.
// Either OK or Err must have non-zero size, e.g. both cannot be struct{} or a zero-length array.
// For results with two zero-length types, use UnsizedResult.
type SizedResult[S Shape[OK] | Shape[Err], OK any, Err any] struct {
v SizedVariant2[S, OK, Err]
}

// IsErr returns true if r holds the error value.
func (r *SizedResult[S, OK, Err]) IsErr() bool {
return r.v.isT1
}

// SetOK stores the OK value in r.
func (r *SizedResult[S, OK, Err]) SetOK(ok OK) {
r.v.Set0(ok)
}

// SetErr stores the error value in r.
func (r *SizedResult[S, OK, Err]) SetErr(err Err) {
r.v.Set1(err)
}

// OK returns the OK value for r and true if r represents the OK state.
// If r represents an error, then the zero value of OK is returned.
func (r *SizedResult[S, OK, Err]) OK() (ok OK, isOK bool) {
return r.v.V0()
}

// Err returns the error value for r and true if r represents the error state.
// If r represents an OK value, then the zero value of Err is returned.
func (r *SizedResult[S, OK, Err]) Err() (err Err, isErr bool) {
return r.v.V1()
}

type UnsizedResult[OK any, Err any] struct {
v UnsizedVariant2[OK, Err]
}

// IsErr returns true if r holds the error value.
func (r *UnsizedResult[OK, Err]) IsErr() bool {
return bool(r.v)
}

// SetErr stores the OK value in r.
func (r *UnsizedResult[OK, Err]) SetOK(ok OK) {
r.v.Set0(ok)
}

// SetErr stores the error value in r.
func (r *UnsizedResult[OK, Err]) SetErr(err Err) {
r.v.Set1(err)
}

// OK returns the OK value for r and true if r represents the OK state.
// If r represents an error, then the zero value of OK is returned.
func (r *UnsizedResult[OK, Err]) OK() (ok OK, isOK bool) {
return r.v.V0()
}

// Err returns the error value for r and true if r represents the error state.
// If r represents an OK value, then the zero value of Err is returned.
func (r *UnsizedResult[OK, Err]) Err() (err Err, isErr bool) {
return r.v.V1()
}

// UntypedResult represents an untyped Component Model result, e.g.
// result or result<_, _>. The OK and Err types are both struct{}.
type UntypedResult struct {
UnsizedResult[struct{}, struct{}]
}
Loading

0 comments on commit fd44f01

Please sign in to comment.