compiler: fix equally named structs in different scopes
For example, in this code: type kv struct { v float32 } func foo(a *kv) { type kv struct { v byte } } Both 'kv' types would be given the same LLVM type, even though they are different types! This is fixed by only creating a LLVM type once per Go type (types.Type). As an added bonus, this change gives a performance improvement of about 0.4%. Not that much, but certainly not nothing for such a small change.
Этот коммит содержится в:
родитель
d348db4a0d
коммит
409688e67a
3 изменённых файлов: 52 добавлений и 10 удалений
|
@ -23,7 +23,7 @@ import (
|
|||
// Version of the compiler pacakge. Must be incremented each time the compiler
|
||||
// package changes in a way that affects the generated LLVM module.
|
||||
// This version is independent of the TinyGo version number.
|
||||
const Version = 17 // last change: add math.arch* aliases
|
||||
const Version = 18 // last change: fix duplicated named structs
|
||||
|
||||
func init() {
|
||||
llvm.InitializeAllTargets()
|
||||
|
@ -74,6 +74,7 @@ type compilerContext struct {
|
|||
cu llvm.Metadata
|
||||
difiles map[string]llvm.Metadata
|
||||
ditypes map[types.Type]llvm.Metadata
|
||||
llvmTypes map[types.Type]llvm.Type
|
||||
machine llvm.TargetMachine
|
||||
targetData llvm.TargetData
|
||||
intType llvm.Type
|
||||
|
@ -94,6 +95,7 @@ func newCompilerContext(moduleName string, machine llvm.TargetMachine, config *C
|
|||
DumpSSA: dumpSSA,
|
||||
difiles: make(map[string]llvm.Metadata),
|
||||
ditypes: make(map[types.Type]llvm.Metadata),
|
||||
llvmTypes: make(map[types.Type]llvm.Type),
|
||||
machine: machine,
|
||||
targetData: machine.CreateTargetData(),
|
||||
astComments: map[string]*ast.CommentGroup{},
|
||||
|
@ -315,10 +317,23 @@ func (c *compilerContext) getLLVMRuntimeType(name string) llvm.Type {
|
|||
return c.getLLVMType(typ)
|
||||
}
|
||||
|
||||
// getLLVMType creates and returns a LLVM type for a Go type. In the case of
|
||||
// named struct types (or Go types implemented as named LLVM structs such as
|
||||
// strings) it also creates it first if necessary.
|
||||
// getLLVMType returns a LLVM type for a Go type. It doesn't recreate already
|
||||
// created types. This is somewhat important for performance, but especially
|
||||
// important for named struct types (which should only be created once).
|
||||
func (c *compilerContext) getLLVMType(goType types.Type) llvm.Type {
|
||||
// Try to load the LLVM type from the cache.
|
||||
if t, ok := c.llvmTypes[goType]; ok {
|
||||
return t
|
||||
}
|
||||
// Not already created, so adding this type to the cache.
|
||||
llvmType := c.makeLLVMType(goType)
|
||||
c.llvmTypes[goType] = llvmType
|
||||
return llvmType
|
||||
}
|
||||
|
||||
// makeLLVMType creates a LLVM type for a Go type. Don't call this, use
|
||||
// getLLVMType instead.
|
||||
func (c *compilerContext) makeLLVMType(goType types.Type) llvm.Type {
|
||||
switch typ := goType.(type) {
|
||||
case *types.Array:
|
||||
elemType := c.getLLVMType(typ.Elem())
|
||||
|
@ -367,12 +382,10 @@ func (c *compilerContext) getLLVMType(goType types.Type) llvm.Type {
|
|||
// LLVM. This is because it is otherwise impossible to create
|
||||
// self-referencing types such as linked lists.
|
||||
llvmName := typ.Obj().Pkg().Path() + "." + typ.Obj().Name()
|
||||
llvmType := c.mod.GetTypeByName(llvmName)
|
||||
if llvmType.IsNil() {
|
||||
llvmType = c.ctx.StructCreateNamed(llvmName)
|
||||
underlying := c.getLLVMType(st)
|
||||
llvmType.StructSetBody(underlying.StructElementTypes(), false)
|
||||
}
|
||||
llvmType := c.ctx.StructCreateNamed(llvmName)
|
||||
c.llvmTypes[goType] = llvmType // avoid infinite recursion
|
||||
underlying := c.getLLVMType(st)
|
||||
llvmType.StructSetBody(underlying.StructElementTypes(), false)
|
||||
return llvmType
|
||||
}
|
||||
return c.getLLVMType(typ.Underlying())
|
||||
|
|
15
compiler/testdata/basic.go
предоставленный
15
compiler/testdata/basic.go
предоставленный
|
@ -55,3 +55,18 @@ func complexMul(x, y complex64) complex64 {
|
|||
}
|
||||
|
||||
// TODO: complexDiv (requires runtime call)
|
||||
|
||||
// A type 'kv' also exists in function foo. Test that these two types don't
|
||||
// conflict with each other.
|
||||
type kv struct {
|
||||
v float32
|
||||
}
|
||||
|
||||
func foo(a *kv) {
|
||||
// Define a new 'kv' type.
|
||||
type kv struct {
|
||||
v byte
|
||||
}
|
||||
// Use this type.
|
||||
func(b *kv) {}(nil)
|
||||
}
|
||||
|
|
14
compiler/testdata/basic.ll
предоставленный
14
compiler/testdata/basic.ll
предоставленный
|
@ -3,6 +3,9 @@ source_filename = "basic.go"
|
|||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||
target triple = "wasm32--wasi"
|
||||
|
||||
%main.kv = type { float }
|
||||
%main.kv.0 = type { i8 }
|
||||
|
||||
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
|
||||
|
||||
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
|
@ -98,3 +101,14 @@ entry:
|
|||
%7 = insertvalue { float, float } %6, float %5, 1
|
||||
ret { float, float } %7
|
||||
}
|
||||
|
||||
define hidden void @main.foo(%main.kv* dereferenceable_or_null(4) %a, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
call void @"main.foo$1"(%main.kv.0* null, i8* undef, i8* undef)
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden void @"main.foo$1"(%main.kv.0* dereferenceable_or_null(1) %b, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче