Skip to content

Commit b47da08

Browse files
committed
fix(wasm): keep native array storage and layout queries consistent
1 parent d27130d commit b47da08

6 files changed

Lines changed: 92 additions & 27 deletions

File tree

‎cl/native_wasm_layout_test.go‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import "unsafe"
2222
type Record struct { Data unsafe.Pointer; Size uintptr; Tag int32 }
2323
//DIRECTIVE
2424
type Outer struct { R Record; Pointers [2]*byte; Words [2]uintptr; Tail [0]uint64 }
25+
//DIRECTIVE
26+
type Words [2]uintptr
2527
type GoRecord struct { Data unsafe.Pointer; Size uintptr; Tag int32 }
2628
type Wrapper struct { B byte; R Record; P *byte }
2729
var _ [12-int(unsafe.Sizeof(Record{}))]byte
@@ -32,6 +34,8 @@ var _ [4-int(unsafe.Offsetof(Record{}.Size))]byte
3234
var _ [int(unsafe.Offsetof(Record{}.Size))-4]byte
3335
var _ [24-int(unsafe.Sizeof([2]Record{}))]byte
3436
var _ [int(unsafe.Sizeof([2]Record{}))-24]byte
37+
var _ [8-int(unsafe.Sizeof(Words{}))]byte
38+
var _ [int(unsafe.Sizeof(Words{}))-8]byte
3539
var _ [32-int(unsafe.Sizeof(Outer{}))]byte
3640
var _ [int(unsafe.Sizeof(Outer{}))-32]byte
3741
var _ [24-int(unsafe.Sizeof(GoRecord{}))]byte
@@ -43,6 +47,7 @@ var _ [int(unsafe.Offsetof(Wrapper{}.P))-16]byte
4347
func Size() uintptr { return unsafe.Sizeof(Record{}) }
4448
func Next(records *[2]Record) *Record { return &records[1] }
4549
func SetSize(r *Record, n uintptr) { r.Size = n }
50+
func SetWord(w *Words, n uintptr) { w[1] = n }
4651
`
4752
for _, directive := range []string{"//llgo:cdecl", "//llgo:type C"} {
4853
for _, target := range []*llssa.Target{

‎internal/build/testdata/wasm-profile/_wrap/profile.c‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,9 @@ profile_record llgo_wasm_profile_record_round_trip(
2121
}
2222

2323
ptrdiff_t llgo_wasm_profile_signed_word(ptrdiff_t value) { return value; }
24+
25+
int llgo_wasm_profile_words(size_t *words) {
26+
if (words[0] != 7 || words[1] != 9) return 0;
27+
words[1] = 11;
28+
return 1;
29+
}

‎internal/build/testdata/wasm-profile/native_record.go‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ type nativeRecord struct {
1414
//llgo:cdecl
1515
type recordCallback func(nativeRecord) nativeRecord
1616

17+
//llgo:cdecl
18+
type nativeWords [2]uintptr
19+
20+
//go:linkname cWords C.llgo_wasm_profile_words
21+
func cWords(*nativeWords) int32
22+
1723
//go:linkname cRecordSize C.llgo_wasm_profile_record_size
1824
func cRecordSize() uintptr
1925

@@ -37,6 +43,10 @@ func updateNativeRecord(r nativeRecord) nativeRecord {
3743
}
3844

3945
func checkNativeRecord() {
46+
words := nativeWords{7, 9}
47+
if unsafe.Sizeof(words) != 2*expectedCPointerSize || cWords(&words) != 1 || words[1] != 11 {
48+
panic("native array layout disagrees with C")
49+
}
4050
if cRecordSize() != unsafe.Sizeof(nativeRecord{}) || cRecordOffset() != unsafe.Offsetof(nativeRecord{}.Size) {
4151
println("native layout", cRecordSize(), unsafe.Sizeof(nativeRecord{}), cRecordOffset(), unsafe.Offsetof(nativeRecord{}.Size))
4252
panic("native aggregate layout disagrees with C")

‎ssa/native_wasm_layout.go‎

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ func (p *goProgram) wasmNativeLayout(t types.Type) (sourceLayout, bool) {
1212
}
1313
switch u := t.Underlying().(type) {
1414
case *types.Array:
15+
// An array's own directive selects C slots. An ordinary Go array of
16+
// native callbacks must retain the callbacks' Go storage width.
17+
if _, named := types.Unalias(t).(*types.Named); named && prog.hasLegacyNativeTypeLayout(t) {
18+
return arraySourceLayout(p.wasmNativeFieldLayout(u.Elem()), u.Len()), true
19+
}
1520
if elem, ok := p.wasmNativeLayout(u.Elem()); ok {
1621
return arraySourceLayout(elem, u.Len()), true
1722
}
@@ -20,26 +25,38 @@ func (p *goProgram) wasmNativeLayout(t types.Type) (sourceLayout, bool) {
2025
if !native && u.NumFields() > 0 {
2126
native = prog.nativeFields([]*types.Var{u.Field(0)})
2227
}
23-
changed := native
24-
for i := 0; i < u.NumFields() && !changed; i++ {
25-
_, changed = p.wasmNativeLayout(u.Field(i).Type())
26-
}
27-
if changed {
28-
layout, _ := p.wasmNativeFieldsLayout(u.NumFields(), u.Field, native)
28+
if native {
29+
layout, _ := p.wasmNativeFieldsLayout(u.NumFields(), u.Field)
2930
return layout, true
3031
}
32+
for i := 0; i < u.NumFields(); i++ {
33+
if known, ok := p.wasmNativeLayout(u.Field(i).Type()); ok {
34+
layout, _ := aggregateSourceLayout(u.NumFields(), func(j int) sourceLayout {
35+
if j == i {
36+
return known
37+
}
38+
return p.wasmGoFieldLayout(u.Field(j).Type())
39+
}, true)
40+
return layout, true
41+
}
42+
}
3143
}
3244
return sourceLayout{}, false
3345
}
3446

35-
func (p *goProgram) wasmNativeFieldsLayout(n int, field func(int) *types.Var, native bool) (sourceLayout, []int64) {
47+
func (p *goProgram) wasmNativeFieldsLayout(n int, field func(int) *types.Var) (sourceLayout, []int64) {
3648
return aggregateSourceLayout(n, func(i int) sourceLayout {
37-
t := field(i).Type()
38-
if native {
39-
return p.wasmNativeFieldLayout(t)
40-
}
41-
return sourceLayout{p.Sizeof(t), p.Alignof(t)}
42-
}, !native)
49+
return p.wasmNativeFieldLayout(field(i).Type())
50+
}, false)
51+
}
52+
53+
// Query a subtree once and retain the size/alignment pair. Do not memoize
54+
// across checker calls, since a named type can still be incomplete.
55+
func (p *goProgram) wasmGoFieldLayout(t types.Type) sourceLayout {
56+
if layout, ok := p.wasmNativeLayout(t); ok {
57+
return layout
58+
}
59+
return sourceLayout{p.goSizeof(t), p.goAlignof(t)}
4360
}
4461

4562
func (p *goProgram) wasmNativeFieldLayout(t types.Type) sourceLayout {
@@ -56,5 +73,5 @@ func (p *goProgram) wasmNativeFieldLayout(t types.Type) sourceLayout {
5673
}
5774
// A nested record owns its storage policy; an outer C directive does not
5875
// recursively turn an ordinary Go record into a C record.
59-
return sourceLayout{p.Sizeof(t), p.Alignof(t)}
76+
return p.wasmGoFieldLayout(t)
6077
}

‎ssa/native_wasm_layout_test.go‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,15 @@ func TestWasmNativeSourceLayoutMatchesStorage(t *testing.T) {
2121
type Inner struct { P *byte; W uintptr }
2222
type Record struct { P *byte; W uintptr; Fn func(); Map map[int]int; Chan chan int; Words [2]uintptr; Inner Inner }
2323
type Wrapper struct { B byte; Record Record; Tail struct{} }
24+
type Words [2]uintptr
25+
type Pointers [2]*byte
2426
`, 0)
2527
if err != nil {
2628
t.Fatal(err)
2729
}
2830
prog.SetTypeCallConv("native.Record", CDecl)
31+
prog.SetTypeCallConv("native.Words", CDecl)
32+
prog.SetTypeCallConv("native.Pointers", CDecl)
2933
for _, decl := range file.Decls {
3034
spec := decl.(*ast.GenDecl).Specs[0].(*ast.TypeSpec)
3135
if spec.Name.Name == "Record" {
@@ -53,6 +57,14 @@ type Wrapper struct { B byte; Record Record; Tail struct{} }
5357
checkHostLayout(t, prog, sizes, raw, tc.size, 8, tc.offsets)
5458
checkHostLayout(t, prog, sizes, types.NewArray(raw, 2), 2*tc.size, 8, nil)
5559
}
60+
for _, name := range []string{"Words", "Pointers"} {
61+
raw := pkg.Scope().Lookup(name).Type()
62+
checkHostLayout(t, prog, sizes, raw, 8, 4, nil)
63+
checkHostLayout(t, prog, sizes, types.NewArray(raw, 2), 16, 4, nil)
64+
if size := prog.SizeOf(prog.Type(raw, CDecl)); size != 8 {
65+
t.Fatalf("%s C storage size = %d, want 8", name, size)
66+
}
67+
}
5668
})
5769
}
5870
}

‎ssa/type.go‎

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ func (p *goProgram) Alignof(T types.Type) int64 {
9696
if layout, ok := p.wasmNativeLayout(T); ok {
9797
return layout.align
9898
}
99+
return p.goAlignof(T)
100+
}
101+
102+
func (p *goProgram) goAlignof(T types.Type) int64 {
99103
if layout, ok := p.hostLayout(T); ok {
100104
return layout.align
101105
}
@@ -109,22 +113,26 @@ func (p *goProgram) Alignof(T types.Type) int64 {
109113
// Offsetsof must implement the offset guarantees required by the spec.
110114
// A negative entry in the result indicates that the struct is too large.
111115
func (p *goProgram) Offsetsof(fields []*types.Var) (ret []int64) {
112-
prog := Program(unsafe.Pointer(p))
116+
prog := Program(p)
117+
native := prog.nativeFields(fields)
113118
if prog.usesWideGoStorage() {
114-
native := prog.nativeFields(fields)
115-
changed := native
116-
for _, f := range fields {
117-
if changed {
118-
break
119-
}
120-
_, changed = p.wasmNativeLayout(f.Type())
121-
}
122-
if changed {
123-
_, offsets := p.wasmNativeFieldsLayout(len(fields), func(i int) *types.Var { return fields[i] }, native)
119+
if native {
120+
_, offsets := p.wasmNativeFieldsLayout(len(fields), func(i int) *types.Var { return fields[i] })
124121
return offsets
125122
}
123+
for i, f := range fields {
124+
if known, ok := p.wasmNativeLayout(f.Type()); ok {
125+
_, offsets := aggregateSourceLayout(len(fields), func(j int) sourceLayout {
126+
if j == i {
127+
return known
128+
}
129+
return p.wasmGoFieldLayout(fields[j].Type())
130+
}, true)
131+
return offsets
132+
}
133+
}
126134
}
127-
if !prog.nativeFields(fields) {
135+
if !native {
128136
for _, f := range fields {
129137
if isHostLayoutMarker(f.Type()) || containsHostLayout(f.Type()) {
130138
_, offsets := p.hostFieldsLayout(fields)
@@ -136,7 +144,7 @@ func (p *goProgram) Offsetsof(fields []*types.Var) (ret []int64) {
136144
if abi.IsClosureFields(fields) {
137145
return []int64{0, ptrSize}
138146
}
139-
if _, offsets, ok := p.vectorStructLayout(fields, prog.nativeFields(fields)); ok {
147+
if _, offsets, ok := p.vectorStructLayout(fields, native); ok {
140148
return offsets
141149
}
142150
extra := int64(0)
@@ -155,6 +163,10 @@ func (p *goProgram) Sizeof(T types.Type) int64 {
155163
if layout, ok := p.wasmNativeLayout(T); ok {
156164
return layout.size
157165
}
166+
return p.goSizeof(T)
167+
}
168+
169+
func (p *goProgram) goSizeof(T types.Type) int64 {
158170
if layout, ok := p.hostLayout(T); ok {
159171
return layout.size
160172
}
@@ -756,6 +768,9 @@ func (p Program) toNamed(raw *types.Named) Type {
756768
return p.toLLVMNamedStruct(name, raw, t, kind)
757769
default:
758770
typ := p.rawType(t)
771+
if _, array := t.(*types.Array); array && p.hasLegacyNativeTypeLayout(raw) {
772+
typ = p.withNativeStorage(typ)
773+
}
759774
return &aType{typ.ll, rawType{raw}, typ.kind}
760775
}
761776
}

0 commit comments

Comments
 (0)