Commit 75e7308b authored by Robert Griesemer's avatar Robert Griesemer

go/types: support for customizable Alignof, Sizeof

(Offsetof is a function of Alignof and Sizeof.)

- removed IntSize, PtrSize from Context (set Sizeof instead)
- GcImporter needs a Context now (it needs to have
  access to Sizeof/Alignof)
- removed exported Size field from Basic (use Sizeof)
- added Offset to Field
- added Alignment, Size to Struct

R=adonovan
CC=golang-dev
https://golang.org/cl/7357046
parent 1e063eea
...@@ -162,13 +162,7 @@ func NewBuilder(mode BuilderMode, loader SourceLoader, errh func(error)) *Builde ...@@ -162,13 +162,7 @@ func NewBuilder(mode BuilderMode, loader SourceLoader, errh func(error)) *Builde
// constants/idents/types maps associated with the containing // constants/idents/types maps associated with the containing
// package so we can discard them once that package is built. // package so we can discard them once that package is built.
b.typechecker = types.Context{ b.typechecker = types.Context{
// TODO(adonovan): permit the client to specify these Error: errh,
// 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,
Expr: func(x ast.Expr, typ types.Type, val interface{}) { Expr: func(x ast.Expr, typ types.Type, val interface{}) {
b.types[x] = typ b.types[x] = typ
if val != nil { if val != nil {
......
...@@ -45,7 +45,7 @@ func (b *Builder) doImport(imports map[string]*types.Package, path string) (typk ...@@ -45,7 +45,7 @@ func (b *Builder) doImport(imports map[string]*types.Package, path string) (typk
} }
var files []*ast.File var files []*ast.File
if b.mode&UseGCImporter != 0 { if b.mode&UseGCImporter != 0 {
typkg, err = types.GcImport(imports, path) typkg, err = types.GcImport(&b.typechecker, imports, path)
} else { } else {
files, err = b.loader(b.Prog.Files, path) files, err = b.loader(b.Prog.Files, path)
if err == nil { if err == nil {
......
...@@ -15,17 +15,15 @@ import ( ...@@ -15,17 +15,15 @@ import (
) )
// A Context specifies the supporting context for type checking. // A Context specifies the supporting context for type checking.
// An empty Context is a ready-to-use default context.
type Context struct { type Context struct {
IntSize int64 // size in bytes of int and uint values // If Error != nil, it is called with each error found
PtrSize int64 // size in bytes of pointers // during type checking. The error strings of errors with
// detailed position information are formatted as follows:
// If Error is not nil, it is called with each error found // filename:line:column: message
// during type checking. Most error messages have accurate
// position information; those error strings are formatted
// filename:line:column: message.
Error func(err error) 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 // denoting an Object in the files provided to Check, and
// obj is the denoted object. // obj is the denoted object.
// Ident is not called for fields and methods in struct or // Ident is not called for fields and methods in struct or
...@@ -35,7 +33,7 @@ type Context struct { ...@@ -35,7 +33,7 @@ type Context struct {
// Objects - than we could lift this restriction. // Objects - than we could lift this restriction.
Ident func(id *ast.Ident, obj Object) 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 // type-checked: typ is the expression type, and val is the value
// if x is constant, val is nil otherwise. // if x is constant, val is nil otherwise.
// //
...@@ -52,8 +50,23 @@ type Context struct { ...@@ -52,8 +50,23 @@ type Context struct {
// represented accurately as an int64. // represented accurately as an int64.
Expr func(x ast.Expr, typ Type, val interface{}) 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 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. // An Importer resolves import paths to Package objects.
...@@ -67,30 +80,16 @@ type Context struct { ...@@ -67,30 +80,16 @@ type Context struct {
// return pkg. // return pkg.
type Importer func(imports map[string]*Package, path string) (pkg *Package, err error) 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 // Check resolves and typechecks a set of package files within the given
// context. The package files' ASTs are augmented by assigning types to // context. If there are no errors, Check returns the package, otherwise
// ast.Objects. If there are no errors, Check returns the package, otherwise
// it returns the first error. If the context's Error handler is nil, // it returns the first error. If the context's Error handler is nil,
// Check terminates as soon as the first error is encountered. // 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) { func (ctxt *Context) Check(fset *token.FileSet, files []*ast.File) (*Package, error) {
return check(ctxt, fset, files) 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) { func Check(fset *token.FileSet, files []*ast.File) (*Package, error) {
return Default.Check(fset, files) var ctxt Context
return ctxt.Check(fset, files)
} }
...@@ -41,7 +41,7 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, bin *builtin, iota ...@@ -41,7 +41,7 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, bin *builtin, iota
if n > 0 { if n > 0 {
arg0 = args[0] arg0 = args[0]
switch id { switch id {
case _Make, _New, _Print, _Println, _Trace: case _Make, _New, _Print, _Println, _Offsetof, _Trace:
// respective cases below do the work // respective cases below do the work
default: default:
// argument must be an expression // argument must be an expression
...@@ -319,27 +319,32 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, bin *builtin, iota ...@@ -319,27 +319,32 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, bin *builtin, iota
case _Alignof: case _Alignof:
x.mode = constant x.mode = constant
x.val = check.ctxt.alignof(x.typ)
x.typ = Typ[Uintptr] 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: case _Offsetof:
if _, ok := unparen(x.expr).(*ast.SelectorExpr); !ok { arg, ok := unparen(arg0).(*ast.SelectorExpr)
check.invalidArg(x.pos(), "%s is not a selector", x) 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 goto Error
} }
x.mode = constant x.mode = constant
x.val = res.offset
x.typ = Typ[Uintptr] 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: case _Sizeof:
x.mode = constant x.mode = constant
x.val = sizeof(check.ctxt, x.typ) x.val = check.ctxt.sizeof(x.typ)
x.typ = Typ[Uintptr] x.typ = Typ[Uintptr]
case _Assert: case _Assert:
...@@ -444,24 +449,92 @@ func (check *checker) complexArg(x *operand) bool { ...@@ -444,24 +449,92 @@ func (check *checker) complexArg(x *operand) bool {
return false 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) { switch typ := underlying(typ).(type) {
case *Basic: case *Array:
switch typ.Kind { // spec: "For a variable x of array type: unsafe.Alignof(x)
case Int, Uint: // is the same as unsafe.Alignof(x[0]), but at least 1."
return ctxt.IntSize return ctxt.alignof(typ.Elt)
case Uintptr: case *Struct:
return ctxt.PtrSize // 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: case *Array:
return sizeof(ctxt, typ.Elt) * typ.Len return ctxt.sizeof(typ.Elt) * typ.Len // may be 0
case *Struct: case *Struct:
var size int64 return typ.Size
for _, f := range typ.Fields { }
size += sizeof(ctxt, f.Type) // 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
} }
...@@ -421,7 +421,9 @@ func check(ctxt *Context, fset *token.FileSet, files []*ast.File) (pkg *Package, ...@@ -421,7 +421,9 @@ func check(ctxt *Context, fset *token.FileSet, files []*ast.File) (pkg *Package,
// resolve identifiers // resolve identifiers
imp := ctxt.Import imp := ctxt.Import
if imp == nil { 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) methods := check.resolve(imp)
......
...@@ -200,7 +200,7 @@ func checkFiles(t *testing.T, testname string, testfiles []string) { ...@@ -200,7 +200,7 @@ func checkFiles(t *testing.T, testname string, testfiles []string) {
files, errlist := parseFiles(t, testname, testfiles) files, errlist := parseFiles(t, testname, testfiles)
// typecheck and collect typechecker errors // typecheck and collect typechecker errors
ctxt := Default var ctxt Context
ctxt.Error = func(err error) { errlist = append(errlist, err) } ctxt.Error = func(err error) { errlist = append(errlist, err) }
ctxt.Check(fset, files) ctxt.Check(fset, files)
......
...@@ -422,7 +422,7 @@ func unaryOpConst(x interface{}, op token.Token, typ *Basic) interface{} { ...@@ -422,7 +422,7 @@ func unaryOpConst(x interface{}, op token.Token, typ *Basic) interface{} {
// thus "too large": We must limit the result size to // thus "too large": We must limit the result size to
// the type's size. // the type's size.
if typ.Info&IsUnsigned != 0 { if typ.Info&IsUnsigned != 0 {
s := uint(typ.Size) * 8 s := uint(typ.size) * 8
if s == 0 { if s == 0 {
// platform-specific type // platform-specific type
// TODO(gri) this needs to be factored out // TODO(gri) this needs to be factored out
......
...@@ -131,21 +131,28 @@ func (check *checker) collectFields(list *ast.FieldList, cycleOk bool) (fields [ ...@@ -131,21 +131,28 @@ func (check *checker) collectFields(list *ast.FieldList, cycleOk bool) (fields [
if list == nil { if list == nil {
return 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 { for _, f := range list.List {
typ := check.typ(f.Type, cycleOk) typ = check.typ(f.Type, cycleOk)
tag := check.tag(f.Tag) tag = check.tag(f.Tag)
if len(f.Names) > 0 { if len(f.Names) > 0 {
// named fields // named fields
for _, name := range f.Names { for _, name := range f.Names {
fields = append(fields, &Field{QualifiedName{check.pkg, name.Name}, typ, tag, false}) add(name.Name, false)
} }
} else { } else {
// anonymous field // anonymous field
switch t := deref(typ).(type) { switch t := deref(typ).(type) {
case *Basic: case *Basic:
fields = append(fields, &Field{QualifiedName{check.pkg, t.Name}, typ, tag, true}) add(t.Name, true)
case *NamedType: case *NamedType:
fields = append(fields, &Field{QualifiedName{check.pkg, t.Obj.GetName()}, typ, tag, true}) add(t.Obj.GetName(), true)
default: default:
if typ != Typ[Invalid] { if typ != Typ[Invalid] {
check.invalidAST(f.Type.Pos(), "anonymous field type %s must be named", typ) check.invalidAST(f.Type.Pos(), "anonymous field type %s must be named", typ)
...@@ -153,9 +160,33 @@ func (check *checker) collectFields(list *ast.FieldList, cycleOk bool) (fields [ ...@@ -153,9 +160,33 @@ func (check *checker) collectFields(list *ast.FieldList, cycleOk bool) (fields [
} }
} }
} }
return 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 type opPredicates map[token.Token]func(Type) bool
var unaryOpPredicates = opPredicates{ var unaryOpPredicates = opPredicates{
...@@ -902,14 +933,14 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle ...@@ -902,14 +933,14 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle
if x.mode == invalid { if x.mode == invalid {
goto Error goto Error
} }
mode, typ := lookupField(x.typ, QualifiedName{check.pkg, sel}) res := lookupField(x.typ, QualifiedName{check.pkg, sel})
if mode == invalid { if res.mode == invalid {
check.invalidOp(e.Pos(), "%s has no single field or method %s", x, sel) check.invalidOp(e.Pos(), "%s has no single field or method %s", x, sel)
goto Error goto Error
} }
if x.mode == typexpr { if x.mode == typexpr {
// method expression // method expression
sig, ok := typ.(*Signature) sig, ok := res.typ.(*Signature)
if !ok { if !ok {
check.invalidOp(e.Pos(), "%s has no method %s", x, sel) check.invalidOp(e.Pos(), "%s has no method %s", x, sel)
goto Error goto Error
...@@ -926,8 +957,8 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle ...@@ -926,8 +957,8 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle
} }
} else { } else {
// regular selector // regular selector
x.mode = mode x.mode = res.mode
x.typ = typ x.typ = res.typ
} }
case *ast.IndexExpr: case *ast.IndexExpr:
...@@ -1242,7 +1273,7 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle ...@@ -1242,7 +1273,7 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle
case *ast.StructType: case *ast.StructType:
x.mode = typexpr 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: case *ast.FuncType:
params, isVariadic := check.collectParams(e.Params, true) params, isVariadic := check.collectParams(e.Params, true)
......
...@@ -82,7 +82,7 @@ func FindPkg(path, srcDir string) (filename, id string) { ...@@ -82,7 +82,7 @@ func FindPkg(path, srcDir string) (filename, id string) {
// can be used directly, and there is no need to call this function (but // can be used directly, and there is no need to call this function (but
// there is also no harm but for extra time used). // 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 // support for gcParser error handling
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
...@@ -91,7 +91,7 @@ func GcImportData(imports map[string]*Package, filename, id string, data *bufio. ...@@ -91,7 +91,7 @@ func GcImportData(imports map[string]*Package, filename, id string, data *bufio.
}() }()
var p gcParser var p gcParser
p.init(filename, id, data, imports) p.init(ctxt, filename, id, data, imports)
pkg = p.parseExport() pkg = p.parseExport()
return return
...@@ -103,7 +103,7 @@ func GcImportData(imports map[string]*Package, filename, id string, data *bufio. ...@@ -103,7 +103,7 @@ func GcImportData(imports map[string]*Package, filename, id string, data *bufio.
// The imports map must contains all packages already imported. // The imports map must contains all packages already imported.
// GcImport satisfies the ast.Importer signature. // 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" { if path == "unsafe" {
return Unsafe, nil return Unsafe, nil
} }
...@@ -145,7 +145,7 @@ func GcImport(imports map[string]*Package, path string) (pkg *Package, err error ...@@ -145,7 +145,7 @@ func GcImport(imports map[string]*Package, path string) (pkg *Package, err error
return return
} }
pkg, err = GcImportData(imports, filename, id, buf) pkg, err = GcImportData(ctxt, imports, filename, id, buf)
return return
} }
...@@ -156,6 +156,7 @@ func GcImport(imports map[string]*Package, path string) (pkg *Package, err error ...@@ -156,6 +156,7 @@ func GcImport(imports map[string]*Package, path string) (pkg *Package, err error
// gcParser parses the exports inside a gc compiler-produced // gcParser parses the exports inside a gc compiler-produced
// object/archive file and populates its scope with the results. // object/archive file and populates its scope with the results.
type gcParser struct { type gcParser struct {
ctxt *Context
scanner scanner.Scanner scanner scanner.Scanner
tok rune // current token tok rune // current token
lit string // literal string; only valid for Ident, Int, String tokens lit string // literal string; only valid for Ident, Int, String tokens
...@@ -163,7 +164,8 @@ type gcParser struct { ...@@ -163,7 +164,8 @@ type gcParser struct {
imports map[string]*Package // package id -> package object 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.Init(src)
p.scanner.Error = func(_ *scanner.Scanner, msg string) { p.error(msg) } 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.scanner.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanChars | scanner.ScanStrings | scanner.ScanComments | scanner.SkipComments
...@@ -492,7 +494,7 @@ func (p *gcParser) parseStructType() Type { ...@@ -492,7 +494,7 @@ func (p *gcParser) parseStructType() Type {
} }
p.expect('}') p.expect('}')
return &Struct{Fields: fields} return p.ctxt.newStruct(fields)
} }
// Parameter = ( identifier | "?" ) [ "..." ] Type [ string_lit ] . // Parameter = ( identifier | "?" ) [ "..." ] Type [ string_lit ] .
......
...@@ -55,7 +55,7 @@ var imports = make(map[string]*Package) ...@@ -55,7 +55,7 @@ var imports = make(map[string]*Package)
func testPath(t *testing.T, path string) bool { func testPath(t *testing.T, path string) bool {
t0 := time.Now() t0 := time.Now()
_, err := GcImport(imports, path) _, err := GcImport(&Context{}, imports, path)
if err != nil { if err != nil {
t.Errorf("testPath(%s): %s", path, err) t.Errorf("testPath(%s): %s", path, err)
return false return false
...@@ -140,7 +140,7 @@ func TestGcImportedTypes(t *testing.T) { ...@@ -140,7 +140,7 @@ func TestGcImportedTypes(t *testing.T) {
importPath := s[0] importPath := s[0]
objName := s[1] objName := s[1]
pkg, err := GcImport(imports, importPath) pkg, err := GcImport(&Context{}, imports, importPath)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue continue
......
...@@ -211,9 +211,13 @@ func (x *operand) isInteger() bool { ...@@ -211,9 +211,13 @@ func (x *operand) isInteger() bool {
x.mode == constant && isRepresentableConst(x.val, UntypedInt) 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 { type lookupResult struct {
mode operandMode mode operandMode
typ Type typ Type
offset int64 // byte offset for struct fields, <0 for methods
} }
type embeddedType struct { type embeddedType struct {
...@@ -234,7 +238,7 @@ func lookupFieldBreadthFirst(list []embeddedType, name QualifiedName) (res looku ...@@ -234,7 +238,7 @@ func lookupFieldBreadthFirst(list []embeddedType, name QualifiedName) (res looku
var next []embeddedType var next []embeddedType
// potentialMatch is invoked every time a match is found. // 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 { if multiples || res.mode != invalid {
// name appeared already at this level - annihilate // name appeared already at this level - annihilate
res.mode = invalid res.mode = invalid
...@@ -243,6 +247,7 @@ func lookupFieldBreadthFirst(list []embeddedType, name QualifiedName) (res looku ...@@ -243,6 +247,7 @@ func lookupFieldBreadthFirst(list []embeddedType, name QualifiedName) (res looku
// first appearance of name // first appearance of name
res.mode = mode res.mode = mode
res.typ = typ res.typ = typ
res.offset = offset
return true return true
} }
...@@ -268,7 +273,7 @@ func lookupFieldBreadthFirst(list []embeddedType, name QualifiedName) (res looku ...@@ -268,7 +273,7 @@ func lookupFieldBreadthFirst(list []embeddedType, name QualifiedName) (res looku
for _, m := range typ.Methods { for _, m := range typ.Methods {
if name.IsSame(m.QualifiedName) { if name.IsSame(m.QualifiedName) {
assert(m.Type != nil) assert(m.Type != nil)
if !potentialMatch(e.multiples, value, m.Type) { if !potentialMatch(e.multiples, value, m.Type, -1) {
return // name collision return // name collision
} }
} }
...@@ -280,7 +285,7 @@ func lookupFieldBreadthFirst(list []embeddedType, name QualifiedName) (res looku ...@@ -280,7 +285,7 @@ func lookupFieldBreadthFirst(list []embeddedType, name QualifiedName) (res looku
for _, f := range t.Fields { for _, f := range t.Fields {
if name.IsSame(f.QualifiedName) { if name.IsSame(f.QualifiedName) {
assert(f.Type != nil) assert(f.Type != nil)
if !potentialMatch(e.multiples, variable, f.Type) { if !potentialMatch(e.multiples, variable, f.Type, f.Offset) {
return // name collision return // name collision
} }
continue continue
...@@ -304,7 +309,7 @@ func lookupFieldBreadthFirst(list []embeddedType, name QualifiedName) (res looku ...@@ -304,7 +309,7 @@ func lookupFieldBreadthFirst(list []embeddedType, name QualifiedName) (res looku
for _, m := range t.Methods { for _, m := range t.Methods {
if name.IsSame(m.QualifiedName) { if name.IsSame(m.QualifiedName) {
assert(m.Type != nil) assert(m.Type != nil)
if !potentialMatch(e.multiples, value, m.Type) { if !potentialMatch(e.multiples, value, m.Type, -1) {
return // name collision return // name collision
} }
} }
...@@ -348,14 +353,14 @@ func findType(list []embeddedType, typ *NamedType) *embeddedType { ...@@ -348,14 +353,14 @@ func findType(list []embeddedType, typ *NamedType) *embeddedType {
return nil return nil
} }
func lookupField(typ Type, name QualifiedName) (operandMode, Type) { func lookupField(typ Type, name QualifiedName) lookupResult {
typ = deref(typ) typ = deref(typ)
if t, ok := typ.(*NamedType); ok { if t, ok := typ.(*NamedType); ok {
for _, m := range t.Methods { for _, m := range t.Methods {
if name.IsSame(m.QualifiedName) { if name.IsSame(m.QualifiedName) {
assert(m.Type != nil) assert(m.Type != nil)
return value, m.Type return lookupResult{value, m.Type, -1}
} }
} }
typ = t.Underlying typ = t.Underlying
...@@ -366,7 +371,7 @@ func lookupField(typ Type, name QualifiedName) (operandMode, Type) { ...@@ -366,7 +371,7 @@ func lookupField(typ Type, name QualifiedName) (operandMode, Type) {
var next []embeddedType var next []embeddedType
for _, f := range t.Fields { for _, f := range t.Fields {
if name.IsSame(f.QualifiedName) { if name.IsSame(f.QualifiedName) {
return variable, f.Type return lookupResult{variable, f.Type, f.Offset}
} }
if f.IsAnonymous { if f.IsAnonymous {
// Possible optimization: If the embedded type // Possible optimization: If the embedded type
...@@ -376,18 +381,17 @@ func lookupField(typ Type, name QualifiedName) (operandMode, Type) { ...@@ -376,18 +381,17 @@ func lookupField(typ Type, name QualifiedName) (operandMode, Type) {
} }
} }
if len(next) > 0 { if len(next) > 0 {
res := lookupFieldBreadthFirst(next, name) return lookupFieldBreadthFirst(next, name)
return res.mode, res.typ
} }
case *Interface: case *Interface:
for _, m := range t.Methods { for _, m := range t.Methods {
if name.IsSame(m.QualifiedName) { if name.IsSame(m.QualifiedName) {
return value, m.Type return lookupResult{value, m.Type, -1}
} }
} }
} }
// not found // not found
return invalid, nil return lookupResult{mode: invalid}
} }
...@@ -281,8 +281,8 @@ func missingMethod(typ Type, T *Interface) (method *Method, wrongType bool) { ...@@ -281,8 +281,8 @@ func missingMethod(typ Type, T *Interface) (method *Method, wrongType bool) {
// Note: This is stronger than the current spec. Should the spec require this? // Note: This is stronger than the current spec. Should the spec require this?
if ityp, _ := underlying(typ).(*Interface); ityp != nil { if ityp, _ := underlying(typ).(*Interface); ityp != nil {
for _, m := range T.Methods { for _, m := range T.Methods {
mode, sig := lookupField(ityp, m.QualifiedName) // TODO(gri) no need to go via lookupField res := lookupField(ityp, m.QualifiedName) // TODO(gri) no need to go via lookupField
if mode != invalid && !IsIdentical(sig, m.Type) { if res.mode != invalid && !IsIdentical(res.typ, m.Type) {
return m, true return m, true
} }
} }
...@@ -291,11 +291,11 @@ func missingMethod(typ Type, T *Interface) (method *Method, wrongType bool) { ...@@ -291,11 +291,11 @@ func missingMethod(typ Type, T *Interface) (method *Method, wrongType bool) {
// a concrete type implements T if it implements all methods of T. // a concrete type implements T if it implements all methods of T.
for _, m := range T.Methods { for _, m := range T.Methods {
mode, sig := lookupField(typ, m.QualifiedName) res := lookupField(typ, m.QualifiedName)
if mode == invalid { if res.mode == invalid {
return m, false return m, false
} }
if !IsIdentical(sig, m.Type) { if !IsIdentical(res.typ, m.Type) {
return m, true return m, true
} }
} }
......
...@@ -64,7 +64,7 @@ func TestResolveQualifiedIdents(t *testing.T) { ...@@ -64,7 +64,7 @@ func TestResolveQualifiedIdents(t *testing.T) {
// resolve and type-check package AST // resolve and type-check package AST
idents := make(map[*ast.Ident]Object) idents := make(map[*ast.Ident]Object)
ctxt := Default var ctxt Context
ctxt.Ident = func(id *ast.Ident, obj Object) { idents[id] = obj } ctxt.Ident = func(id *ast.Ident, obj Object) { idents[id] = obj }
pkg, err := ctxt.Check(fset, files) pkg, err := ctxt.Check(fset, files)
if err != nil { if err != nil {
......
...@@ -269,6 +269,15 @@ func _recover() { ...@@ -269,6 +269,15 @@ func _recover() {
recover() 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() { func _Alignof() {
var x int var x int
_ = unsafe /* ERROR "argument" */ .Alignof() _ = unsafe /* ERROR "argument" */ .Alignof()
...@@ -277,6 +286,13 @@ func _Alignof() { ...@@ -277,6 +286,13 @@ func _Alignof() {
_ = unsafe.Alignof(42) _ = unsafe.Alignof(42)
_ = unsafe.Alignof(new(struct{})) _ = unsafe.Alignof(new(struct{}))
unsafe /* ERROR "not used" */ .Alignof(x) 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() { func _Offsetof() {
...@@ -289,6 +305,13 @@ func _Offsetof() { ...@@ -289,6 +305,13 @@ func _Offsetof() {
_ = unsafe.Offsetof((x.f)) _ = unsafe.Offsetof((x.f))
_ = unsafe.Offsetof((((((((x))).f))))) _ = unsafe.Offsetof((((((((x))).f)))))
unsafe /* ERROR "not used" */ .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() { func _Sizeof() {
...@@ -314,6 +337,14 @@ func _Sizeof() { ...@@ -314,6 +337,14 @@ func _Sizeof() {
assert(unsafe.Sizeof(float64(0)) == 8) assert(unsafe.Sizeof(float64(0)) == 8)
assert(unsafe.Sizeof(complex64(0)) == 8) assert(unsafe.Sizeof(complex64(0)) == 8)
assert(unsafe.Sizeof(complex128(0)) == 16) 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 // self-testing only
......
...@@ -74,7 +74,7 @@ const ( ...@@ -74,7 +74,7 @@ const (
type Basic struct { type Basic struct {
Kind BasicKind Kind BasicKind
Info BasicInfo Info BasicInfo
Size int64 size int64 // use DefaultSizeof to get size
Name string Name string
} }
...@@ -116,12 +116,15 @@ type Field struct { ...@@ -116,12 +116,15 @@ type Field struct {
QualifiedName QualifiedName
Type Type Type Type
Tag string Tag string
Offset int64 // offset within struct, in bytes
IsAnonymous bool IsAnonymous bool
} }
// A Struct represents a struct type struct{...}. // A Struct represents a struct type struct{...}.
type Struct 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 { func (typ *Struct) fieldIndex(name string) int {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment