-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #21 from ydnar/component-model-types
cm: Component Model types in Go
- Loading branch information
Showing
10 changed files
with
592 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package cm | ||
|
||
type ( | ||
Enum8 uint8 | ||
Enum16 uint16 | ||
Enum32 uint32 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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{}] | ||
} |
Oops, something went wrong.