Skip to content

Commit 5c4ace4

Browse files
authored
Merge pull request #353 from bytecodealliance/ydnar/issue350
wit: align record size to max field alignment
2 parents 6bcf449 + cf9588b commit 5c4ace4

16 files changed

Lines changed: 64 additions & 19 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
1111
### Fixed
1212

1313
- [#306](https://github.com/bytecodealliance/go-modules/issues/306): do not emit incompatible version feature gates when serializing WIT. For example, if a world with version `0.1.0` *includes* a world from another package with a `@since(version = 0.2.0)` feature gate, then strip that feature gate when serializing to WIT if the version is greater than the world’s package version.
14+
- [#350](https://github.com/bytecodealliance/go-modules/issues/350): correctly align `record` size to the highest alignment of its fields, per [specification](https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#element-size).
1415

1516

1617
## [v0.6.2] — 2025-03-16

cm/result_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,3 +387,37 @@ func TestIssue344BoolResult(t *testing.T) {
387387
t.Errorf("*v.OK(): %v, expected %v", got, want)
388388
}
389389
}
390+
391+
func TestIssue350(t *testing.T) {
392+
type Shape struct {
393+
_ HostLayout
394+
shape [unsafe.Sizeof(Tuple3[uint16, Result[uint64, uint64, struct{}], uint8]{})]byte
395+
// Previously, with bug in (*Record).Size() algorithm:
396+
// shape [unsafe.Sizeof(Option[[3]string]{})]byte
397+
}
398+
type O Option[[3]string]
399+
type E Tuple3[uint16, Result[uint64, uint64, struct{}], uint8]
400+
type T Result[Shape, O, E]
401+
402+
shapeSize := unsafe.Sizeof(Shape{})
403+
errSize := unsafe.Sizeof(E{})
404+
405+
if errSize > shapeSize {
406+
t.Errorf("size of err type (%d) > size of shape type (%d)", errSize, shapeSize)
407+
408+
var e E
409+
base := uintptr(unsafe.Pointer(&e))
410+
f0 := uintptr(unsafe.Pointer(&e.F0)) - base
411+
f1 := uintptr(unsafe.Pointer(&e.F1)) - base
412+
f2 := uintptr(unsafe.Pointer(&e.F2)) - base
413+
t.Logf("Offsets: F0: %d F1: %d F2 %d", f0, f1, f2)
414+
}
415+
416+
// _ = Err[T](
417+
// Err, uint8]{
418+
// F0: 0,
419+
// F1: OK[Result[uint64, uint64, struct{}]](0),
420+
// F2: 0,
421+
// },
422+
// )
423+
}

wit/abi.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
// [Canonical ABI] [size], [alignment], and [flat] representation.
1010
//
1111
// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md
12-
// [size]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#size
12+
// [size]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#element-size
1313
// [alignment]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#alignment
1414
// [flat]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#flattening
1515
type ABI interface {

wit/abi_test.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ func TestDiscriminant(t *testing.T) {
9999
}
100100
}
101101

102-
func TestTypeSize(t *testing.T) {
102+
func TestTypeSizeAndAlign(t *testing.T) {
103103
tests := []struct {
104104
name string
105105
v Type
@@ -119,6 +119,10 @@ func TestTypeSize(t *testing.T) {
119119
{"f64", F64{}, 8, 8},
120120
{"char", Char{}, 4, 4},
121121
{"string", String{}, 8, 4},
122+
{"option<string>", &TypeDef{Kind: &Option{Type: String{}}}, 12, 4},
123+
{"option<f32>", &TypeDef{Kind: &Option{Type: F32{}}}, 8, 4},
124+
{"variant", &TypeDef{Kind: &Variant{Cases: []Case{{Type: String{}}, {Type: F64{}}}}}, 16, 8},
125+
{"record", &TypeDef{Kind: &Record{Fields: []Field{{Type: U16{}}, {Type: &TypeDef{Kind: &Result{OK: U64{}}}}, {Type: U8{}}}}}, 32, 8},
122126
}
123127
for _, tt := range tests {
124128
t.Run(tt.name, func(t *testing.T) {
@@ -156,6 +160,7 @@ func TestTypeFlat(t *testing.T) {
156160
{"option<string>", &TypeDef{Kind: &Option{Type: String{}}}, []Type{U32{}, PointerTo(U8{}), U32{}}},
157161
{"option<f32>", &TypeDef{Kind: &Option{Type: F32{}}}, []Type{U32{}, F32{}}},
158162
{"variant", &TypeDef{Kind: &Variant{Cases: []Case{{Type: String{}}, {Type: F64{}}}}}, []Type{U32{}, U64{}, U32{}}},
163+
{"record", &TypeDef{Kind: &Record{Fields: []Field{{Type: U16{}}, {Type: &TypeDef{Kind: &Result{OK: U64{}}}}, {Type: U8{}}}}}, []Type{U32{}, U32{}, U64{}, U32{}}},
159164
}
160165
for _, tt := range tests {
161166
t.Run(tt.name, func(t *testing.T) {
@@ -177,9 +182,6 @@ func witFor[T Node](nodes ...T) []string {
177182

178183
// TestHasBorrow verifies that HasBorrow returns true for WIT types that contain a Borrow type.
179184
func TestHasBorrow(t *testing.T) {
180-
makeBorrow := func() *TypeDef { return &TypeDef{Kind: &Borrow{}} }
181-
makeTypeDef := func(kind TypeDefKind) *TypeDef { return &TypeDef{Kind: kind} }
182-
183185
testCases := []struct {
184186
name string
185187
typeDef *TypeDef
@@ -211,3 +213,11 @@ func TestHasBorrow(t *testing.T) {
211213
})
212214
}
213215
}
216+
217+
func makeTypeDef(kind TypeDefKind) *TypeDef {
218+
return &TypeDef{Kind: kind}
219+
}
220+
221+
func makeBorrow() *TypeDef {
222+
return &TypeDef{Kind: &Borrow{}}
223+
}

wit/enum.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func (e *Enum) Despecialize() TypeDefKind {
2929
// type that can represent 0...len(e.Cases).
3030
// It is first [despecialized] into a [Variant] with no associated types, then sized.
3131
//
32-
// [ABI byte size]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#size
32+
// [ABI byte size]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#element-size
3333
// [despecialized]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#despecialization
3434
func (e *Enum) Size() uintptr {
3535
return e.Despecialize().Size()

wit/flags.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ type Flags struct {
1111

1212
// Size returns the [ABI byte size] of [Flags] f.
1313
//
14-
// [ABI byte size]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#size
14+
// [ABI byte size]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#element-size
1515
func (f *Flags) Size() uintptr {
1616
n := len(f.Flags)
1717
switch {

wit/future.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ type Future struct {
1212

1313
// Size returns the [ABI byte size] for a [Future].
1414
//
15-
// [ABI byte size]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#size
15+
// [ABI byte size]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#element-size
1616
func (*Future) Size() uintptr { return 4 }
1717

1818
// Align returns the [ABI byte alignment] a [Future].

wit/list.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ type List struct {
1111

1212
// Size returns the [ABI byte size] for a [List].
1313
//
14-
// [ABI byte size]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#size
14+
// [ABI byte size]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#element-size
1515
func (*List) Size() uintptr { return 8 } // [2]int32
1616

1717
// Align returns the [ABI byte alignment] a [List].

wit/option.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func (o *Option) Despecialize() TypeDefKind {
2727
// Size returns the [ABI byte size] for [Option] o.
2828
// It is first [despecialized] into a [Variant] with two cases, "none" and "some(T)", then sized.
2929
//
30-
// [ABI byte size]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#size
30+
// [ABI byte size]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#element-size
3131
// [despecialized]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#despecialization
3232
func (o *Option) Size() uintptr {
3333
return o.Despecialize().Size()

wit/pointer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ type Pointer struct {
1414

1515
// Size returns the [ABI byte size] for [Pointer].
1616
//
17-
// [ABI byte size]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#size
17+
// [ABI byte size]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#element-size
1818
func (*Pointer) Size() uintptr { return 4 }
1919

2020
// Align returns the [ABI byte alignment] for [Pointer].

0 commit comments

Comments
 (0)