// constants/idents/types maps associated with the containing
// package so we can discard them once that package is built.
b.typechecker = types.Context{
- // TODO(adonovan): permit the client to specify these
- // values. Perhaps expose the types.Context parameter
- // directly (though of course we'll have to override
- // the Expr/Ident/Import callbacks).
- IntSize: 8,
- PtrSize: 8,
- Error: errh,
+ Error: errh,
Expr: func(x ast.Expr, typ types.Type, val interface{}) {
b.types[x] = typ
if val != nil {
}
var files []*ast.File
if b.mode&UseGCImporter != 0 {
- typkg, err = types.GcImport(imports, path)
+ typkg, err = types.GcImport(&b.typechecker, imports, path)
} else {
files, err = b.loader(b.Prog.Files, path)
if err == nil {
)
// A Context specifies the supporting context for type checking.
+// An empty Context is a ready-to-use default context.
type Context struct {
- IntSize int64 // size in bytes of int and uint values
- PtrSize int64 // size in bytes of pointers
-
- // If Error is not nil, it is called with each error found
- // during type checking. Most error messages have accurate
- // position information; those error strings are formatted
- // filename:line:column: message.
+ // If Error != nil, it is called with each error found
+ // during type checking. The error strings of errors with
+ // detailed position information are formatted as follows:
+ // filename:line:column: message
Error func(err error)
- // If Ident is not nil, it is called for each identifier id
+ // If Ident != nil, it is called for each identifier id
// denoting an Object in the files provided to Check, and
// obj is the denoted object.
// Ident is not called for fields and methods in struct or
// Objects - than we could lift this restriction.
Ident func(id *ast.Ident, obj Object)
- // If Expr is not nil, it is called for each expression x that is
+ // If Expr != nil, it is called for each expression x that is
// type-checked: typ is the expression type, and val is the value
// if x is constant, val is nil otherwise.
//
// represented accurately as an int64.
Expr func(x ast.Expr, typ Type, val interface{})
- // If Import is not nil, it is used instead of GcImport.
+ // If Import != nil, it is called for each imported package.
+ // Otherwise, GcImporter is called.
Import Importer
+
+ // If Alignof != nil, it is called to determine alignment.
+ // Otherwise DefaultAlignmentof is called.
+ // Alignof must return a size > 0, in bytes. It is not called
+ // for arrays and structs (those alignments are based on the
+ // alignment of the array elements or struct fields, respectively).
+ Alignof func(Type) int64
+
+ // If Sizeof != nil, it is called to determine sizes of types.
+ // Otherwise, DefaultSizeof is called.
+ // Sizeof must return a size >= 0, in bytes. It is not called
+ // for arrays and structs (those sizes are based on the sizes
+ // of the array elements or struct fields, respectively).
+ Sizeof func(Type) int64
}
// An Importer resolves import paths to Package objects.
// return pkg.
type Importer func(imports map[string]*Package, path string) (pkg *Package, err error)
-// Default is the default context for type checking.
-var Default = Context{
- // TODO(gri) Perhaps this should depend on GOARCH?
- IntSize: 8,
- PtrSize: 8,
-}
-
// Check resolves and typechecks a set of package files within the given
-// context. The package files' ASTs are augmented by assigning types to
-// ast.Objects. If there are no errors, Check returns the package, otherwise
+// context. If there are no errors, Check returns the package, otherwise
// it returns the first error. If the context's Error handler is nil,
// Check terminates as soon as the first error is encountered.
-//
-// CAUTION: At the moment, the returned *ast.Package only contains the package
-// name and scope - the other fields are not set up. The returned
-// *Package contains the name and imports (but no scope yet). Once
-// we have the scope moved from *ast.Scope to *Scope, only *Package
-// will be returned.
-//
func (ctxt *Context) Check(fset *token.FileSet, files []*ast.File) (*Package, error) {
return check(ctxt, fset, files)
}
-// Check is shorthand for Default.Check.
+// Check is shorthand for ctxt.Check where ctxt is a default (empty) context.
func Check(fset *token.FileSet, files []*ast.File) (*Package, error) {
- return Default.Check(fset, files)
+ var ctxt Context
+ return ctxt.Check(fset, files)
}
if n > 0 {
arg0 = args[0]
switch id {
- case _Make, _New, _Print, _Println, _Trace:
+ case _Make, _New, _Print, _Println, _Offsetof, _Trace:
// respective cases below do the work
default:
// argument must be an expression
case _Alignof:
x.mode = constant
+ x.val = check.ctxt.alignof(x.typ)
x.typ = Typ[Uintptr]
- // For now we return 1 always as it satisfies the spec's alignment guarantees.
- // TODO(gri) Extend typechecker API so that platform-specific values can be
- // provided.
- x.val = int64(1)
case _Offsetof:
- if _, ok := unparen(x.expr).(*ast.SelectorExpr); !ok {
- check.invalidArg(x.pos(), "%s is not a selector", x)
+ arg, ok := unparen(arg0).(*ast.SelectorExpr)
+ if !ok {
+ check.invalidArg(arg0.Pos(), "%s is not a selector expression", arg0)
+ goto Error
+ }
+ check.expr(x, arg.X, nil, -1)
+ if x.mode == invalid {
+ goto Error
+ }
+ sel := arg.Sel.Name
+ res := lookupField(x.typ, QualifiedName{check.pkg, arg.Sel.Name})
+ if res.mode != variable {
+ check.invalidArg(x.pos(), "%s has no single field %s", x, sel)
goto Error
}
x.mode = constant
+ x.val = res.offset
x.typ = Typ[Uintptr]
- // because of the size guarantees for basic types (> 0 for some),
- // returning 0 is only correct if two distinct non-zero size
- // structs can have the same address (the spec permits that)
- x.val = int64(0)
case _Sizeof:
x.mode = constant
- x.val = sizeof(check.ctxt, x.typ)
+ x.val = check.ctxt.sizeof(x.typ)
x.typ = Typ[Uintptr]
case _Assert:
return false
}
-func sizeof(ctxt *Context, typ Type) int64 {
+func (ctxt *Context) alignof(typ Type) int64 {
+ // For arrays and structs, alignment is defined in terms
+ // of alignment of the elements and fields, respectively.
switch typ := underlying(typ).(type) {
- case *Basic:
- switch typ.Kind {
- case Int, Uint:
- return ctxt.IntSize
- case Uintptr:
- return ctxt.PtrSize
+ case *Array:
+ // spec: "For a variable x of array type: unsafe.Alignof(x)
+ // is the same as unsafe.Alignof(x[0]), but at least 1."
+ return ctxt.alignof(typ.Elt)
+ case *Struct:
+ // spec: "For a variable x of struct type: unsafe.Alignof(x)
+ // is the largest of of the values unsafe.Alignof(x.f) for
+ // each field f of x, but at least 1."
+ return typ.Alignment
+ }
+ // externally defined Alignof
+ if f := ctxt.Alignof; f != nil {
+ if a := f(typ); a > 0 {
+ return a
}
- return typ.Size
+ panic("Context.Alignof returned value < 1")
+ }
+ // all other cases
+ return DefaultAlignof(typ)
+}
+
+// DefaultMaxAlign is the default maximum alignment, in bytes,
+// used by DefaultAlignof.
+const DefaultMaxAlign = 8
+
+// DefaultAlignof implements the default alignment computation
+// for unsafe.Alignof. It is used if Context.Alignof == nil.
+func DefaultAlignof(typ Type) int64 {
+ a := DefaultSizeof(typ) // may be 0
+ // spec: "For a variable x of any type: unsafe.Alignof(x) is at least 1."
+ if a < 1 {
+ return 1
+ }
+ if a > DefaultMaxAlign {
+ return DefaultMaxAlign
+ }
+ return a
+}
+
+func (ctxt *Context) sizeof(typ Type) int64 {
+ // For arrays and structs, size is defined in terms
+ // of size of the elements and fields, respectively.
+ switch typ := underlying(typ).(type) {
case *Array:
- return sizeof(ctxt, typ.Elt) * typ.Len
+ return ctxt.sizeof(typ.Elt) * typ.Len // may be 0
case *Struct:
- var size int64
- for _, f := range typ.Fields {
- size += sizeof(ctxt, f.Type)
+ return typ.Size
+ }
+ // externally defined Sizeof
+ if f := ctxt.Sizeof; f != nil {
+ if s := f(typ); s >= 0 {
+ return s
+ }
+ panic("Context.Sizeof returned value < 0")
+ }
+ // all other cases
+ return DefaultSizeof(typ)
+}
+
+// DefaultPtrSize is the default size of pointers, in bytes,
+// used by DefaultSizeof.
+const DefaultPtrSize = 8
+
+// DefaultSizeof implements the default size computation
+// for unsafe.Sizeof. It is used if Context.Sizeof == nil.
+func DefaultSizeof(typ Type) int64 {
+ switch typ := underlying(typ).(type) {
+ case *Basic:
+ if s := typ.size; s > 0 {
+ return s
+ }
+ if typ.Kind == String {
+ return DefaultPtrSize * 2
}
- return size
+ case *Array:
+ return DefaultSizeof(typ.Elt) * typ.Len // may be 0
+ case *Slice:
+ return DefaultPtrSize * 3
+ case *Struct:
+ return typ.Size // may be 0
+ case *Signature:
+ return DefaultPtrSize * 2
}
- return ctxt.PtrSize // good enough
+ return DefaultPtrSize // catch-all
}
// resolve identifiers
imp := ctxt.Import
if imp == nil {
- imp = GcImport
+ imp = func(imports map[string]*Package, path string) (pkg *Package, err error) {
+ return GcImport(ctxt, imports, path)
+ }
}
methods := check.resolve(imp)
files, errlist := parseFiles(t, testname, testfiles)
// typecheck and collect typechecker errors
- ctxt := Default
+ var ctxt Context
ctxt.Error = func(err error) { errlist = append(errlist, err) }
ctxt.Check(fset, files)
// thus "too large": We must limit the result size to
// the type's size.
if typ.Info&IsUnsigned != 0 {
- s := uint(typ.Size) * 8
+ s := uint(typ.size) * 8
if s == 0 {
// platform-specific type
// TODO(gri) this needs to be factored out
if list == nil {
return
}
+
+ var typ Type // current field typ
+ var tag string // current field tag
+ add := func(name string, isAnonymous bool) {
+ fields = append(fields, &Field{QualifiedName{check.pkg, name}, typ, tag, 0, isAnonymous})
+ }
+
for _, f := range list.List {
- typ := check.typ(f.Type, cycleOk)
- tag := check.tag(f.Tag)
+ typ = check.typ(f.Type, cycleOk)
+ tag = check.tag(f.Tag)
if len(f.Names) > 0 {
// named fields
for _, name := range f.Names {
- fields = append(fields, &Field{QualifiedName{check.pkg, name.Name}, typ, tag, false})
+ add(name.Name, false)
}
} else {
// anonymous field
switch t := deref(typ).(type) {
case *Basic:
- fields = append(fields, &Field{QualifiedName{check.pkg, t.Name}, typ, tag, true})
+ add(t.Name, true)
case *NamedType:
- fields = append(fields, &Field{QualifiedName{check.pkg, t.Obj.GetName()}, typ, tag, true})
+ add(t.Obj.GetName(), true)
default:
if typ != Typ[Invalid] {
check.invalidAST(f.Type.Pos(), "anonymous field type %s must be named", typ)
}
}
}
+
return
}
+// align returns the smallest y >= x such that y % a == 0.
+func align(x, a int64) int64 {
+ y := x + a - 1
+ return y - y%a
+}
+
+func (ctxt *Context) newStruct(fields []*Field) *Struct {
+ // spec: "For a variable x of struct type: unsafe.Alignof(x) is the largest of
+ // of the values unsafe.Alignof(x.f) for each field f of x, but at least 1."
+ maxAlign := int64(1)
+ var offset int64
+ for _, f := range fields {
+ a := ctxt.alignof(f.Type)
+ if a > maxAlign {
+ maxAlign = a
+ }
+ offset = align(offset, a)
+ f.Offset = offset
+ offset += ctxt.sizeof(f.Type)
+ }
+ return &Struct{fields, maxAlign, offset}
+}
+
type opPredicates map[token.Token]func(Type) bool
var unaryOpPredicates = opPredicates{
if x.mode == invalid {
goto Error
}
- mode, typ := lookupField(x.typ, QualifiedName{check.pkg, sel})
- if mode == invalid {
+ res := lookupField(x.typ, QualifiedName{check.pkg, sel})
+ if res.mode == invalid {
check.invalidOp(e.Pos(), "%s has no single field or method %s", x, sel)
goto Error
}
if x.mode == typexpr {
// method expression
- sig, ok := typ.(*Signature)
+ sig, ok := res.typ.(*Signature)
if !ok {
check.invalidOp(e.Pos(), "%s has no method %s", x, sel)
goto Error
}
} else {
// regular selector
- x.mode = mode
- x.typ = typ
+ x.mode = res.mode
+ x.typ = res.typ
}
case *ast.IndexExpr:
case *ast.StructType:
x.mode = typexpr
- x.typ = &Struct{Fields: check.collectFields(e.Fields, cycleOk)}
+ x.typ = check.ctxt.newStruct(check.collectFields(e.Fields, cycleOk))
case *ast.FuncType:
params, isVariadic := check.collectParams(e.Params, true)
// can be used directly, and there is no need to call this function (but
// there is also no harm but for extra time used).
//
-func GcImportData(imports map[string]*Package, filename, id string, data *bufio.Reader) (pkg *Package, err error) {
+func GcImportData(ctxt *Context, imports map[string]*Package, filename, id string, data *bufio.Reader) (pkg *Package, err error) {
// support for gcParser error handling
defer func() {
if r := recover(); r != nil {
}()
var p gcParser
- p.init(filename, id, data, imports)
+ p.init(ctxt, filename, id, data, imports)
pkg = p.parseExport()
return
// The imports map must contains all packages already imported.
// GcImport satisfies the ast.Importer signature.
//
-func GcImport(imports map[string]*Package, path string) (pkg *Package, err error) {
+func GcImport(ctxt *Context, imports map[string]*Package, path string) (pkg *Package, err error) {
if path == "unsafe" {
return Unsafe, nil
}
return
}
- pkg, err = GcImportData(imports, filename, id, buf)
+ pkg, err = GcImportData(ctxt, imports, filename, id, buf)
return
}
// gcParser parses the exports inside a gc compiler-produced
// object/archive file and populates its scope with the results.
type gcParser struct {
+ ctxt *Context
scanner scanner.Scanner
tok rune // current token
lit string // literal string; only valid for Ident, Int, String tokens
imports map[string]*Package // package id -> package object
}
-func (p *gcParser) init(filename, id string, src io.Reader, imports map[string]*Package) {
+func (p *gcParser) init(ctxt *Context, filename, id string, src io.Reader, imports map[string]*Package) {
+ p.ctxt = ctxt
p.scanner.Init(src)
p.scanner.Error = func(_ *scanner.Scanner, msg string) { p.error(msg) }
p.scanner.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanChars | scanner.ScanStrings | scanner.ScanComments | scanner.SkipComments
}
p.expect('}')
- return &Struct{Fields: fields}
+ return p.ctxt.newStruct(fields)
}
// Parameter = ( identifier | "?" ) [ "..." ] Type [ string_lit ] .
func testPath(t *testing.T, path string) bool {
t0 := time.Now()
- _, err := GcImport(imports, path)
+ _, err := GcImport(&Context{}, imports, path)
if err != nil {
t.Errorf("testPath(%s): %s", path, err)
return false
importPath := s[0]
objName := s[1]
- pkg, err := GcImport(imports, importPath)
+ pkg, err := GcImport(&Context{}, imports, importPath)
if err != nil {
t.Error(err)
continue
x.mode == constant && isRepresentableConst(x.val, UntypedInt)
}
+// lookupResult represents the result of a struct field/method lookup.
+// TODO(gri) mode (variable for fields vs value for methods) and offset
+// (>= 0 vs <0) provide redundant data - simplify!
type lookupResult struct {
- mode operandMode
- typ Type
+ mode operandMode
+ typ Type
+ offset int64 // byte offset for struct fields, <0 for methods
}
type embeddedType struct {
var next []embeddedType
// potentialMatch is invoked every time a match is found.
- potentialMatch := func(multiples bool, mode operandMode, typ Type) bool {
+ potentialMatch := func(multiples bool, mode operandMode, typ Type, offset int64) bool {
if multiples || res.mode != invalid {
// name appeared already at this level - annihilate
res.mode = invalid
// first appearance of name
res.mode = mode
res.typ = typ
+ res.offset = offset
return true
}
for _, m := range typ.Methods {
if name.IsSame(m.QualifiedName) {
assert(m.Type != nil)
- if !potentialMatch(e.multiples, value, m.Type) {
+ if !potentialMatch(e.multiples, value, m.Type, -1) {
return // name collision
}
}
for _, f := range t.Fields {
if name.IsSame(f.QualifiedName) {
assert(f.Type != nil)
- if !potentialMatch(e.multiples, variable, f.Type) {
+ if !potentialMatch(e.multiples, variable, f.Type, f.Offset) {
return // name collision
}
continue
for _, m := range t.Methods {
if name.IsSame(m.QualifiedName) {
assert(m.Type != nil)
- if !potentialMatch(e.multiples, value, m.Type) {
+ if !potentialMatch(e.multiples, value, m.Type, -1) {
return // name collision
}
}
return nil
}
-func lookupField(typ Type, name QualifiedName) (operandMode, Type) {
+func lookupField(typ Type, name QualifiedName) lookupResult {
typ = deref(typ)
if t, ok := typ.(*NamedType); ok {
for _, m := range t.Methods {
if name.IsSame(m.QualifiedName) {
assert(m.Type != nil)
- return value, m.Type
+ return lookupResult{value, m.Type, -1}
}
}
typ = t.Underlying
var next []embeddedType
for _, f := range t.Fields {
if name.IsSame(f.QualifiedName) {
- return variable, f.Type
+ return lookupResult{variable, f.Type, f.Offset}
}
if f.IsAnonymous {
// Possible optimization: If the embedded type
}
}
if len(next) > 0 {
- res := lookupFieldBreadthFirst(next, name)
- return res.mode, res.typ
+ return lookupFieldBreadthFirst(next, name)
}
case *Interface:
for _, m := range t.Methods {
if name.IsSame(m.QualifiedName) {
- return value, m.Type
+ return lookupResult{value, m.Type, -1}
}
}
}
// not found
- return invalid, nil
+ return lookupResult{mode: invalid}
}
// Note: This is stronger than the current spec. Should the spec require this?
if ityp, _ := underlying(typ).(*Interface); ityp != nil {
for _, m := range T.Methods {
- mode, sig := lookupField(ityp, m.QualifiedName) // TODO(gri) no need to go via lookupField
- if mode != invalid && !IsIdentical(sig, m.Type) {
+ res := lookupField(ityp, m.QualifiedName) // TODO(gri) no need to go via lookupField
+ if res.mode != invalid && !IsIdentical(res.typ, m.Type) {
return m, true
}
}
// a concrete type implements T if it implements all methods of T.
for _, m := range T.Methods {
- mode, sig := lookupField(typ, m.QualifiedName)
- if mode == invalid {
+ res := lookupField(typ, m.QualifiedName)
+ if res.mode == invalid {
return m, false
}
- if !IsIdentical(sig, m.Type) {
+ if !IsIdentical(res.typ, m.Type) {
return m, true
}
}
// resolve and type-check package AST
idents := make(map[*ast.Ident]Object)
- ctxt := Default
+ var ctxt Context
ctxt.Ident = func(id *ast.Ident, obj Object) { idents[id] = obj }
pkg, err := ctxt.Check(fset, files)
if err != nil {
recover()
}
+// assuming types.DefaultPtrSize == 8
+type S struct{ // offset
+ a bool // 0
+ b rune // 4
+ c *int // 8
+ d bool // 16
+ e complex128 // 24
+} // 40
+
func _Alignof() {
var x int
_ = unsafe /* ERROR "argument" */ .Alignof()
_ = unsafe.Alignof(42)
_ = unsafe.Alignof(new(struct{}))
unsafe /* ERROR "not used" */ .Alignof(x)
+
+ var y S
+ assert(unsafe.Alignof(y.a) == 1)
+ assert(unsafe.Alignof(y.b) == 4)
+ assert(unsafe.Alignof(y.c) == 8)
+ assert(unsafe.Alignof(y.d) == 1)
+ assert(unsafe.Alignof(y.e) == 8)
}
func _Offsetof() {
_ = unsafe.Offsetof((x.f))
_ = unsafe.Offsetof((((((((x))).f)))))
unsafe /* ERROR "not used" */ .Offsetof(x.f)
+
+ var y S
+ assert(unsafe.Offsetof(y.a) == 0)
+ assert(unsafe.Offsetof(y.b) == 4)
+ assert(unsafe.Offsetof(y.c) == 8)
+ assert(unsafe.Offsetof(y.d) == 16)
+ assert(unsafe.Offsetof(y.e) == 24)
}
func _Sizeof() {
assert(unsafe.Sizeof(float64(0)) == 8)
assert(unsafe.Sizeof(complex64(0)) == 8)
assert(unsafe.Sizeof(complex128(0)) == 16)
+
+ var y S
+ assert(unsafe.Sizeof(y.a) == 1)
+ assert(unsafe.Sizeof(y.b) == 4)
+ assert(unsafe.Sizeof(y.c) == 8)
+ assert(unsafe.Sizeof(y.d) == 1)
+ assert(unsafe.Sizeof(y.e) == 16)
+ assert(unsafe.Sizeof(y) == 40)
}
// self-testing only
type Basic struct {
Kind BasicKind
Info BasicInfo
- Size int64
+ size int64 // use DefaultSizeof to get size
Name string
}
QualifiedName
Type Type
Tag string
+ Offset int64 // offset within struct, in bytes
IsAnonymous bool
}
// A Struct represents a struct type struct{...}.
type Struct struct {
- Fields []*Field
+ Fields []*Field
+ Alignment int64 // struct alignment in bytes
+ Size int64 // struct size in bytes
}
func (typ *Struct) fieldIndex(name string) int {