Skip to content

Commit 80127a7

Browse files
committed
[dev.typeparams] cmd/compile/internal/types2: adjust unsafe.Alignof/Offsetof/Sizeof
Changed the implementation such that the result is a variable rather than a constant if the argument type (or the struct in case of unsafe.Offsetof) has a size that depends on type parameters. Minor unrelated adjustments. For #40301. Change-Id: I1e988f1479b95648ad95a455c764ead829d75749 Reviewed-on: https://go-review.googlesource.com/c/go/+/335413 Trust: Robert Griesemer <[email protected]> Run-TryBot: Robert Griesemer <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent fca3e5c commit 80127a7

File tree

6 files changed

+182
-29
lines changed

6 files changed

+182
-29
lines changed

src/cmd/compile/internal/types2/builtins.go

+54-19
Original file line numberDiff line numberDiff line change
@@ -624,19 +624,22 @@ func (check *Checker) builtin(x *operand, call *syntax.CallExpr, id builtinId) (
624624

625625
case _Alignof:
626626
// unsafe.Alignof(x T) uintptr
627-
if asTypeParam(x.typ) != nil {
628-
check.errorf(call, invalidOp+"unsafe.Alignof undefined for %s", x)
629-
return
630-
}
631627
check.assignment(x, nil, "argument to unsafe.Alignof")
632628
if x.mode == invalid {
633629
return
634630
}
635631

636-
x.mode = constant_
637-
x.val = constant.MakeInt64(check.conf.alignof(x.typ))
632+
if hasVarSize(x.typ) {
633+
x.mode = value
634+
if check.Types != nil {
635+
check.recordBuiltinType(call.Fun, makeSig(Typ[Uintptr], x.typ))
636+
}
637+
} else {
638+
x.mode = constant_
639+
x.val = constant.MakeInt64(check.conf.alignof(x.typ))
640+
// result is constant - no need to record signature
641+
}
638642
x.typ = Typ[Uintptr]
639-
// result is constant - no need to record signature
640643

641644
case _Offsetof:
642645
// unsafe.Offsetof(x T) uintptr, where x must be a selector
@@ -674,30 +677,43 @@ func (check *Checker) builtin(x *operand, call *syntax.CallExpr, id builtinId) (
674677
return
675678
}
676679

677-
// TODO(gri) Should we pass x.typ instead of base (and indirect report if derefStructPtr indirected)?
680+
// TODO(gri) Should we pass x.typ instead of base (and have indirect report if derefStructPtr indirected)?
678681
check.recordSelection(selx, FieldVal, base, obj, index, false)
679682

680-
offs := check.conf.offsetof(base, index)
681-
x.mode = constant_
682-
x.val = constant.MakeInt64(offs)
683+
// The field offset is considered a variable even if the field is declared before
684+
// the part of the struct which is variable-sized. This makes both the rules
685+
// simpler and also permits (or at least doesn't prevent) a compiler from re-
686+
// arranging struct fields if it wanted to.
687+
if hasVarSize(base) {
688+
x.mode = value
689+
if check.Types != nil {
690+
check.recordBuiltinType(call.Fun, makeSig(Typ[Uintptr], obj.Type()))
691+
}
692+
} else {
693+
x.mode = constant_
694+
x.val = constant.MakeInt64(check.conf.offsetof(base, index))
695+
// result is constant - no need to record signature
696+
}
683697
x.typ = Typ[Uintptr]
684-
// result is constant - no need to record signature
685698

686699
case _Sizeof:
687700
// unsafe.Sizeof(x T) uintptr
688-
if asTypeParam(x.typ) != nil {
689-
check.errorf(call, invalidOp+"unsafe.Sizeof undefined for %s", x)
690-
return
691-
}
692701
check.assignment(x, nil, "argument to unsafe.Sizeof")
693702
if x.mode == invalid {
694703
return
695704
}
696705

697-
x.mode = constant_
698-
x.val = constant.MakeInt64(check.conf.sizeof(x.typ))
706+
if hasVarSize(x.typ) {
707+
x.mode = value
708+
if check.Types != nil {
709+
check.recordBuiltinType(call.Fun, makeSig(Typ[Uintptr], x.typ))
710+
}
711+
} else {
712+
x.mode = constant_
713+
x.val = constant.MakeInt64(check.conf.sizeof(x.typ))
714+
// result is constant - no need to record signature
715+
}
699716
x.typ = Typ[Uintptr]
700-
// result is constant - no need to record signature
701717

702718
case _Slice:
703719
// unsafe.Slice(ptr *T, len IntegerType) []T
@@ -769,6 +785,25 @@ func (check *Checker) builtin(x *operand, call *syntax.CallExpr, id builtinId) (
769785
return true
770786
}
771787

788+
// hasVarSize reports if the size of type t is variable due to type parameters.
789+
func hasVarSize(t Type) bool {
790+
switch t := under(t).(type) {
791+
case *Array:
792+
return hasVarSize(t.elem)
793+
case *Struct:
794+
for _, f := range t.fields {
795+
if hasVarSize(f.typ) {
796+
return true
797+
}
798+
}
799+
case *TypeParam:
800+
return true
801+
case *Named, *Union, *instance, *top:
802+
unreachable()
803+
}
804+
return false
805+
}
806+
772807
// applyTypeFunc applies f to x. If x is a type parameter,
773808
// the result is a type parameter constrained by an new
774809
// interface bound. The type bounds for that interface

src/cmd/compile/internal/types2/builtins_test.go

+11-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package types2_test
77
import (
88
"cmd/compile/internal/syntax"
99
"fmt"
10+
"strings"
1011
"testing"
1112

1213
. "cmd/compile/internal/types2"
@@ -111,12 +112,15 @@ var builtinCalls = []struct {
111112

112113
{"Alignof", `_ = unsafe.Alignof(0)`, `invalid type`}, // constant
113114
{"Alignof", `var x struct{}; _ = unsafe.Alignof(x)`, `invalid type`}, // constant
115+
{"Alignof", `var x P; _ = unsafe.Alignof(x)`, `func(p.P₁) uintptr`},
114116

115117
{"Offsetof", `var x struct{f bool}; _ = unsafe.Offsetof(x.f)`, `invalid type`}, // constant
116118
{"Offsetof", `var x struct{_ int; f bool}; _ = unsafe.Offsetof((&x).f)`, `invalid type`}, // constant
119+
{"Offsetof", `var x struct{_ int; f P}; _ = unsafe.Offsetof((&x).f)`, `func(p.P₁) uintptr`},
117120

118121
{"Sizeof", `_ = unsafe.Sizeof(0)`, `invalid type`}, // constant
119122
{"Sizeof", `var x struct{}; _ = unsafe.Sizeof(x)`, `invalid type`}, // constant
123+
{"Sizeof", `var x P; _ = unsafe.Sizeof(x)`, `func(p.P₁) uintptr`},
120124

121125
{"Slice", `var p *int; _ = unsafe.Slice(p, 1)`, `func(*int, int) []int`},
122126
{"Slice", `var p *byte; var n uintptr; _ = unsafe.Slice(p, n)`, `func(*byte, uintptr) []byte`},
@@ -149,9 +153,14 @@ func TestBuiltinSignatures(t *testing.T) {
149153
}
150154
}
151155

156+
func parseGenericSrc(path, src string) (*syntax.File, error) {
157+
errh := func(error) {} // dummy error handler so that parsing continues in presence of errors
158+
return syntax.Parse(syntax.NewFileBase(path), strings.NewReader(src), errh, nil, syntax.AllowGenerics)
159+
}
160+
152161
func testBuiltinSignature(t *testing.T, name, src0, want string) {
153-
src := fmt.Sprintf(`package p; import "unsafe"; type _ unsafe.Pointer /* use unsafe */; func _() { %s }`, src0)
154-
f, err := parseSrc("", src)
162+
src := fmt.Sprintf(`package p; import "unsafe"; type _ unsafe.Pointer /* use unsafe */; func _[P any]() { %s }`, src0)
163+
f, err := parseGenericSrc("", src)
155164
if err != nil {
156165
t.Errorf("%s: %s", src0, err)
157166
return

src/cmd/compile/internal/types2/infer.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -401,8 +401,8 @@ func (check *Checker) inferB(tparams []*TypeName, targs []Type, report bool) (ty
401401

402402
// u.x.types() now contains the incoming type arguments plus any additional type
403403
// arguments for which there were structural constraints. The newly inferred non-
404-
// nil entries may still contain references to other type parameters. For instance,
405-
// for [A any, B interface{type []C}, C interface{type *A}], if A == int
404+
// nil entries may still contain references to other type parameters.
405+
// For instance, for [A any, B interface{ []C }, C interface{ *A }], if A == int
406406
// was given, unification produced the type list [int, []C, *A]. We eliminate the
407407
// remaining type parameters by substituting the type parameters in this type list
408408
// until nothing changes anymore.

src/cmd/compile/internal/types2/sizes.go

+6-4
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ type StdSizes struct {
4848
func (s *StdSizes) Alignof(T Type) int64 {
4949
// For arrays and structs, alignment is defined in terms
5050
// of alignment of the elements and fields, respectively.
51-
switch t := optype(T).(type) {
51+
switch t := under(T).(type) {
5252
case *Array:
5353
// spec: "For a variable x of array type: unsafe.Alignof(x)
5454
// is the same as unsafe.Alignof(x[0]), but at least 1."
@@ -73,6 +73,8 @@ func (s *StdSizes) Alignof(T Type) int64 {
7373
if t.Info()&IsString != 0 {
7474
return s.WordSize
7575
}
76+
case *TypeParam, *Union:
77+
unreachable()
7678
}
7779
a := s.Sizeof(T) // may be 0
7880
// spec: "For a variable x of any type: unsafe.Alignof(x) is at least 1."
@@ -118,7 +120,7 @@ var basicSizes = [...]byte{
118120
}
119121

120122
func (s *StdSizes) Sizeof(T Type) int64 {
121-
switch t := optype(T).(type) {
123+
switch t := under(T).(type) {
122124
case *Basic:
123125
assert(isTyped(T))
124126
k := t.kind
@@ -148,10 +150,10 @@ func (s *StdSizes) Sizeof(T Type) int64 {
148150
}
149151
offsets := s.Offsetsof(t.fields)
150152
return offsets[n-1] + s.Sizeof(t.fields[n-1].typ)
151-
case *Union:
152-
panic("Sizeof unimplemented for union")
153153
case *Interface:
154154
return s.WordSize * 2
155+
case *TypeParam, *Union:
156+
unreachable()
155157
}
156158
return s.WordSize // catch-all
157159
}

src/cmd/compile/internal/types2/testdata/check/builtins.go2

+107
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
package builtins
88

9+
import "unsafe"
10+
911
// close
1012

1113
type C0 interface{ int }
@@ -127,3 +129,108 @@ func _[T Bss]() {
127129
_ = make(T, 10)
128130
_ = make(T, 10, 20)
129131
}
132+
133+
// unsafe.Alignof
134+
135+
func _[T comparable]() {
136+
var (
137+
b int64
138+
a [10]T
139+
s struct{ f T }
140+
p *T
141+
l []T
142+
f func(T)
143+
i interface{ m() T }
144+
c chan T
145+
m map[T]T
146+
t T
147+
)
148+
149+
const bb = unsafe.Alignof(b)
150+
assert(bb == 8)
151+
const _ = unsafe /* ERROR not constant */ .Alignof(a)
152+
const _ = unsafe /* ERROR not constant */ .Alignof(s)
153+
const pp = unsafe.Alignof(p)
154+
assert(pp == 8)
155+
const ll = unsafe.Alignof(l)
156+
assert(ll == 8)
157+
const ff = unsafe.Alignof(f)
158+
assert(ff == 8)
159+
const ii = unsafe.Alignof(i)
160+
assert(ii == 8)
161+
const cc = unsafe.Alignof(c)
162+
assert(cc == 8)
163+
const mm = unsafe.Alignof(m)
164+
assert(mm == 8)
165+
const _ = unsafe /* ERROR not constant */ .Alignof(t)
166+
}
167+
168+
// unsafe.Offsetof
169+
170+
func _[T comparable]() {
171+
var (
172+
b struct{ _, f int64 }
173+
a struct{ _, f [10]T }
174+
s struct{ _, f struct{ f T } }
175+
p struct{ _, f *T }
176+
l struct{ _, f []T }
177+
f struct{ _, f func(T) }
178+
i struct{ _, f interface{ m() T } }
179+
c struct{ _, f chan T }
180+
m struct{ _, f map[T]T }
181+
t struct{ _, f T }
182+
)
183+
184+
const bb = unsafe.Offsetof(b.f)
185+
assert(bb == 8)
186+
const _ = unsafe /* ERROR not constant */ .Alignof(a)
187+
const _ = unsafe /* ERROR not constant */ .Alignof(s)
188+
const pp = unsafe.Offsetof(p.f)
189+
assert(pp == 8)
190+
const ll = unsafe.Offsetof(l.f)
191+
assert(ll == 24)
192+
const ff = unsafe.Offsetof(f.f)
193+
assert(ff == 8)
194+
const ii = unsafe.Offsetof(i.f)
195+
assert(ii == 16)
196+
const cc = unsafe.Offsetof(c.f)
197+
assert(cc == 8)
198+
const mm = unsafe.Offsetof(m.f)
199+
assert(mm == 8)
200+
const _ = unsafe /* ERROR not constant */ .Alignof(t)
201+
}
202+
203+
// unsafe.Sizeof
204+
205+
func _[T comparable]() {
206+
var (
207+
b int64
208+
a [10]T
209+
s struct{ f T }
210+
p *T
211+
l []T
212+
f func(T)
213+
i interface{ m() T }
214+
c chan T
215+
m map[T]T
216+
t T
217+
)
218+
219+
const bb = unsafe.Sizeof(b)
220+
assert(bb == 8)
221+
const _ = unsafe /* ERROR not constant */ .Alignof(a)
222+
const _ = unsafe /* ERROR not constant */ .Alignof(s)
223+
const pp = unsafe.Sizeof(p)
224+
assert(pp == 8)
225+
const ll = unsafe.Sizeof(l)
226+
assert(ll == 24)
227+
const ff = unsafe.Sizeof(f)
228+
assert(ff == 8)
229+
const ii = unsafe.Sizeof(i)
230+
assert(ii == 16)
231+
const cc = unsafe.Sizeof(c)
232+
assert(cc == 8)
233+
const mm = unsafe.Sizeof(m)
234+
assert(mm == 8)
235+
const _ = unsafe /* ERROR not constant */ .Alignof(t)
236+
}

src/cmd/compile/internal/types2/testdata/fixedbugs/issue40301.go2

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ package p
77
import "unsafe"
88

99
func _[T any](x T) {
10-
_ = unsafe /* ERROR undefined */ .Alignof(x)
11-
_ = unsafe /* ERROR undefined */ .Sizeof(x)
10+
_ = unsafe.Alignof(x)
11+
_ = unsafe.Sizeof(x)
1212
}

0 commit comments

Comments
 (0)