Skip to content

Commit dd029f4

Browse files
authored
Merge pull request #349 from bytecodealliance/ydnar/issue344
cm: do not use bool discriminant in variant, result, and option types
2 parents 9d80dee + e20904d commit dd029f4

11 files changed

Lines changed: 244 additions & 25 deletions

File tree

Makefile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ generated: clean json
2222
clean:
2323
rm -rf ./generated/*
2424
rm -f internal/wasmtools/wasm-tools.wasm
25-
rm -f internal/wasmtools/wasm-tools.wasm.gz
2625

2726
# tests/generated writes generated Go code to the tests directory
2827
.PHONY: tests/generated

cm/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
88

99
- Mutating methods `SetOK` and `SetErr` on `result` types (`Result[Shape, OK, Err]`).
1010

11+
### Changed
12+
13+
- Breaking: `BoolResult` is now represented as a `uint8` rather than a `bool`. This fixes an issue with TinyGo where `bool` values are treated distinctly from `uint8`. See [#344](https://github.com/bytecodealliance/go-modules/issues/344) for more information.
14+
15+
### Fixed
16+
17+
- [#344](https://github.com/bytecodealliance/go-modules/issues/344): the memory representation of `option` and `result` now use `uint8` instead of `bool` for the discriminator. LLVM optimizes `bool` values into a single bit, which breaks WIT variants where the associated types share memory.
18+
1119
## [v0.2.2] — 2025-03-16
1220

1321
### Fixed

cm/option.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,30 +18,30 @@ func None[T any]() Option[T] {
1818
func Some[T any](v T) Option[T] {
1919
return Option[T]{
2020
option: option[T]{
21-
isSome: true,
21+
isSome: 1,
2222
some: v,
2323
},
2424
}
2525
}
2626

2727
// option represents the internal representation of a Component Model option type.
28-
// The first byte is a bool representing none or some,
28+
// The first byte is a byte representing the none or some case,
2929
// followed by storage for the associated type T.
3030
type option[T any] struct {
3131
_ HostLayout
32-
isSome bool
32+
isSome uint8
3333
some T
3434
}
3535

3636
// None returns true if o represents the none case.
3737
func (o *option[T]) None() bool {
38-
return !o.isSome
38+
return o.isSome == 0
3939
}
4040

4141
// Some returns a non-nil *T if o represents the some case,
4242
// or nil if o represents the none case.
4343
func (o *option[T]) Some() *T {
44-
if o.isSome {
44+
if o.isSome == 1 {
4545
return &o.some
4646
}
4747
return nil
@@ -51,7 +51,7 @@ func (o *option[T]) Some() *T {
5151
// or the zero value of T if o represents the none case.
5252
// This does not have a pointer receiver, so it can be chained.
5353
func (o option[T]) Value() T {
54-
if !o.isSome {
54+
if o.isSome == 0 {
5555
var zero T
5656
return zero
5757
}

cm/result.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ import "unsafe"
44

55
const (
66
// ResultOK represents the OK case of a result.
7-
ResultOK = false
7+
ResultOK = 0
88

99
// ResultErr represents the error case of a result.
10-
ResultErr = true
10+
ResultErr = 1
1111
)
1212

1313
// BoolResult represents a result with no OK or error type.
1414
// False represents the OK case and true represents the error case.
15-
type BoolResult bool
15+
type BoolResult uint8
1616

1717
// Result represents a result sized to hold the Shape type.
1818
// The size of the Shape type must be greater than or equal to the size of OK and Err types.
@@ -33,7 +33,7 @@ type AnyResult[Shape, OK, Err any] interface {
3333
// result represents the internal representation of a Component Model result type.
3434
type result[Shape, OK, Err any] struct {
3535
_ HostLayout
36-
isErr bool
36+
isErr uint8
3737
_ [0]OK
3838
_ [0]Err
3939
data Shape // [unsafe.Sizeof(*(*Shape)(unsafe.Pointer(nil)))]byte
@@ -76,20 +76,20 @@ func (r *result[Shape, OK, Err]) SetErr(err Err) {
7676
// IsOK returns true if r represents the OK case.
7777
func (r *result[Shape, OK, Err]) IsOK() bool {
7878
r.validate()
79-
return !r.isErr
79+
return r.isErr == ResultOK
8080
}
8181

8282
// IsErr returns true if r represents the error case.
8383
func (r *result[Shape, OK, Err]) IsErr() bool {
8484
r.validate()
85-
return r.isErr
85+
return r.isErr == ResultErr
8686
}
8787

8888
// OK returns a non-nil *OK pointer if r represents the OK case.
8989
// If r represents an error, then it returns nil.
9090
func (r *result[Shape, OK, Err]) OK() *OK {
9191
r.validate()
92-
if r.isErr {
92+
if r.isErr == ResultErr {
9393
return nil
9494
}
9595
return (*OK)(unsafe.Pointer(&r.data))
@@ -99,7 +99,7 @@ func (r *result[Shape, OK, Err]) OK() *OK {
9999
// If r represents the OK case, then it returns nil.
100100
func (r *result[Shape, OK, Err]) Err() *Err {
101101
r.validate()
102-
if !r.isErr {
102+
if r.isErr == ResultOK {
103103
return nil
104104
}
105105
return (*Err)(unsafe.Pointer(&r.data))
@@ -109,7 +109,7 @@ func (r *result[Shape, OK, Err]) Err() *Err {
109109
// or (zero value of OK, Err, true) if r represents the error case.
110110
// This does not have a pointer receiver, so it can be chained.
111111
func (r result[Shape, OK, Err]) Result() (ok OK, err Err, isErr bool) {
112-
if r.isErr {
112+
if r.isErr == ResultErr {
113113
return ok, *(*Err)(unsafe.Pointer(&r.data)), true
114114
}
115115
return *(*OK)(unsafe.Pointer(&r.data)), err, false

cm/result_test.go

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ func TestResultLayout(t *testing.T) {
8787
size uintptr
8888
offset uintptr
8989
}{
90-
{"result", BoolResult(false), 1, 0},
90+
{"result", BoolResult(0), 1, 0},
9191
{"ok", BoolResult(ResultOK), 1, 0},
9292
{"err", BoolResult(ResultErr), 1, 0},
9393

@@ -350,3 +350,40 @@ func TestIssue284NotTinyGo(t *testing.T) {
350350
}
351351
}
352352
}
353+
354+
func TestIssue344Option(t *testing.T) {
355+
type T Result[Option[uint64], uint64, Option[uint64]]
356+
357+
want := uint64(2)
358+
v := T(OK[Result[Option[uint64], uint64, Option[uint64]]](want))
359+
got := *v.OK()
360+
361+
if got != want {
362+
t.Errorf("*v.OK(): %v, expected %v", got, want)
363+
}
364+
}
365+
366+
func TestIssue344Result(t *testing.T) {
367+
type R Result[uint64, uint64, bool]
368+
type T Result[R, uint64, R]
369+
370+
want := uint64(2)
371+
v := T(OK[T](want))
372+
got := *v.OK()
373+
374+
if got != want {
375+
t.Errorf("*v.OK(): %v, expected %v", got, want)
376+
}
377+
}
378+
379+
func TestIssue344BoolResult(t *testing.T) {
380+
type T Result[BoolResult, uint8, BoolResult]
381+
382+
want := uint8(2)
383+
v := T(OK[T](want))
384+
got := *v.OK()
385+
386+
if got != want {
387+
t.Errorf("*v.OK(): %v, expected %v", got, want)
388+
}
389+
}

testdata/issues/issue344.wit

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package issues:issue344;
2+
3+
world w {
4+
import i;
5+
}
6+
7+
interface i {
8+
type a = result<u64, tuple<option<u64>>>;
9+
type b = result<u64, tuple<result<u64, bool>>>;
10+
type c = result<result, u8>;
11+
12+
fa: func() -> a;
13+
fb: func() -> b;
14+
fc: func() -> c;
15+
}

testdata/issues/issue344.wit.json

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
{
2+
"worlds": [
3+
{
4+
"name": "w",
5+
"imports": {
6+
"interface-0": {
7+
"interface": {
8+
"id": 0
9+
}
10+
}
11+
},
12+
"exports": {},
13+
"package": 0
14+
}
15+
],
16+
"interfaces": [
17+
{
18+
"name": "i",
19+
"types": {
20+
"a": 2,
21+
"b": 5,
22+
"c": 7
23+
},
24+
"functions": {
25+
"fa": {
26+
"name": "fa",
27+
"kind": "freestanding",
28+
"params": [],
29+
"result": 2
30+
},
31+
"fb": {
32+
"name": "fb",
33+
"kind": "freestanding",
34+
"params": [],
35+
"result": 5
36+
},
37+
"fc": {
38+
"name": "fc",
39+
"kind": "freestanding",
40+
"params": [],
41+
"result": 7
42+
}
43+
},
44+
"package": 0
45+
}
46+
],
47+
"types": [
48+
{
49+
"name": null,
50+
"kind": {
51+
"option": "u64"
52+
},
53+
"owner": null
54+
},
55+
{
56+
"name": null,
57+
"kind": {
58+
"tuple": {
59+
"types": [
60+
0
61+
]
62+
}
63+
},
64+
"owner": null
65+
},
66+
{
67+
"name": "a",
68+
"kind": {
69+
"result": {
70+
"ok": "u64",
71+
"err": 1
72+
}
73+
},
74+
"owner": {
75+
"interface": 0
76+
}
77+
},
78+
{
79+
"name": null,
80+
"kind": {
81+
"result": {
82+
"ok": "u64",
83+
"err": "bool"
84+
}
85+
},
86+
"owner": null
87+
},
88+
{
89+
"name": null,
90+
"kind": {
91+
"tuple": {
92+
"types": [
93+
3
94+
]
95+
}
96+
},
97+
"owner": null
98+
},
99+
{
100+
"name": "b",
101+
"kind": {
102+
"result": {
103+
"ok": "u64",
104+
"err": 4
105+
}
106+
},
107+
"owner": {
108+
"interface": 0
109+
}
110+
},
111+
{
112+
"name": null,
113+
"kind": {
114+
"result": {
115+
"ok": null,
116+
"err": null
117+
}
118+
},
119+
"owner": null
120+
},
121+
{
122+
"name": "c",
123+
"kind": {
124+
"result": {
125+
"ok": 6,
126+
"err": "u8"
127+
}
128+
},
129+
"owner": {
130+
"interface": 0
131+
}
132+
}
133+
],
134+
"packages": [
135+
{
136+
"name": "issues:issue344",
137+
"interfaces": {
138+
"i": 0
139+
},
140+
"worlds": {
141+
"w": 0
142+
}
143+
}
144+
]
145+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package issues:issue344;
2+
3+
interface i {
4+
type a = result<u64, tuple<option<u64>>>;
5+
type b = result<u64, tuple<result<u64, bool>>>;
6+
type c = result<result, u8>;
7+
fa: func() -> a;
8+
fb: func() -> b;
9+
fc: func() -> c;
10+
}
11+
12+
world w {
13+
import i;
14+
}

tests/generated/wasi/cli/v0.2.0/exit/exit.wit.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/generated/wasi/cli/v0.2.0/run/run.wasm.go

Lines changed: 1 addition & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)