compiler: remove SimpleDCE pass
The SimpleDCE pass was previously used to only compile the parts of the program that were in use. However, lately the only real purpose has been to speed up the compiler a bit by only compiling the necessary functions. This pass however is a problem for compiling (and caching) packages in parallel. Therefore, this commit removes it as a preparatory step towards that goal.
Этот коммит содержится в:
родитель
b5205cc3ca
коммит
dc1ff80e10
3 изменённых файлов: 132 добавлений и 245 удалений
|
@ -90,6 +90,7 @@ func newCompilerContext(moduleName string, machine llvm.TargetMachine, config *C
|
||||||
ditypes: make(map[types.Type]llvm.Metadata),
|
ditypes: make(map[types.Type]llvm.Metadata),
|
||||||
machine: machine,
|
machine: machine,
|
||||||
targetData: machine.CreateTargetData(),
|
targetData: machine.CreateTargetData(),
|
||||||
|
astComments: map[string]*ast.CommentGroup{},
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ctx = llvm.NewContext()
|
c.ctx = llvm.NewContext()
|
||||||
|
@ -251,12 +252,6 @@ func CompileProgram(lprogram *loader.Program, machine llvm.TargetMachine, config
|
||||||
c.program.Build()
|
c.program.Build()
|
||||||
c.runtimePkg = c.program.ImportedPackage("runtime").Pkg
|
c.runtimePkg = c.program.ImportedPackage("runtime").Pkg
|
||||||
|
|
||||||
// Run a simple dead code elimination pass.
|
|
||||||
functions, err := c.simpleDCE(lprogram)
|
|
||||||
if err != nil {
|
|
||||||
return llvm.Module{}, []error{err}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize debug information.
|
// Initialize debug information.
|
||||||
if c.Debug {
|
if c.Debug {
|
||||||
c.cu = c.dibuilder.CreateCompileUnit(llvm.DICompileUnit{
|
c.cu = c.dibuilder.CreateCompileUnit(llvm.DICompileUnit{
|
||||||
|
@ -268,27 +263,22 @@ func CompileProgram(lprogram *loader.Program, machine llvm.TargetMachine, config
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
c.loadASTComments(lprogram)
|
for _, pkg := range lprogram.Sorted() {
|
||||||
|
c.loadASTComments(pkg)
|
||||||
|
}
|
||||||
|
|
||||||
// Predeclare the runtime.alloc function, which is used by the wordpack
|
// Predeclare the runtime.alloc function, which is used by the wordpack
|
||||||
// functionality.
|
// functionality.
|
||||||
c.getFunction(c.program.ImportedPackage("runtime").Members["alloc"].(*ssa.Function))
|
c.getFunction(c.program.ImportedPackage("runtime").Members["alloc"].(*ssa.Function))
|
||||||
|
|
||||||
// Add definitions to declarations.
|
// Define all functions.
|
||||||
var initFuncs []llvm.Value
|
var initFuncs []llvm.Value
|
||||||
irbuilder := c.ctx.NewBuilder()
|
irbuilder := c.ctx.NewBuilder()
|
||||||
defer irbuilder.Dispose()
|
defer irbuilder.Dispose()
|
||||||
for _, f := range functions {
|
for _, pkg := range lprogram.Sorted() {
|
||||||
if f.Synthetic == "package initializer" {
|
ssaPkg := c.program.Package(pkg.Pkg)
|
||||||
initFuncs = append(initFuncs, c.getFunction(f))
|
c.createPackage(irbuilder, ssaPkg)
|
||||||
}
|
initFuncs = append(initFuncs, c.getFunction(ssaPkg.Members["init"].(*ssa.Function)))
|
||||||
if f.Blocks == nil {
|
|
||||||
continue // external function
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the function definition.
|
|
||||||
b := newBuilder(c, irbuilder, f)
|
|
||||||
b.createFunction()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// After all packages are imported, add a synthetic initializer function
|
// After all packages are imported, add a synthetic initializer function
|
||||||
|
@ -341,38 +331,10 @@ func CompilePackage(moduleName string, pkg *loader.Package, machine llvm.TargetM
|
||||||
ssaPkg := pkg.LoadSSA()
|
ssaPkg := pkg.LoadSSA()
|
||||||
ssaPkg.Build()
|
ssaPkg.Build()
|
||||||
|
|
||||||
// Sort by position, so that the order of the functions in the IR matches
|
// Compile all functions, methods, and global variables in this package.
|
||||||
// the order of functions in the source file. This is useful for testing,
|
|
||||||
// for example.
|
|
||||||
var members []string
|
|
||||||
for name := range ssaPkg.Members {
|
|
||||||
members = append(members, name)
|
|
||||||
}
|
|
||||||
sort.Slice(members, func(i, j int) bool {
|
|
||||||
iPos := ssaPkg.Members[members[i]].Pos()
|
|
||||||
jPos := ssaPkg.Members[members[j]].Pos()
|
|
||||||
if i == j {
|
|
||||||
// Cannot sort by pos, so do it by name.
|
|
||||||
return members[i] < members[j]
|
|
||||||
}
|
|
||||||
return iPos < jPos
|
|
||||||
})
|
|
||||||
|
|
||||||
// Define all functions.
|
|
||||||
irbuilder := c.ctx.NewBuilder()
|
irbuilder := c.ctx.NewBuilder()
|
||||||
defer irbuilder.Dispose()
|
defer irbuilder.Dispose()
|
||||||
for _, name := range members {
|
c.createPackage(irbuilder, ssaPkg)
|
||||||
member := ssaPkg.Members[name]
|
|
||||||
switch member := member.(type) {
|
|
||||||
case *ssa.Function:
|
|
||||||
if member.Blocks == nil {
|
|
||||||
continue // external function
|
|
||||||
}
|
|
||||||
// Create the function definition.
|
|
||||||
b := newBuilder(c, irbuilder, member)
|
|
||||||
b.createFunction()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.mod, nil
|
return c.mod, nil
|
||||||
}
|
}
|
||||||
|
@ -759,6 +721,83 @@ func (c *compilerContext) getDIFile(filename string) llvm.Metadata {
|
||||||
return c.difiles[filename]
|
return c.difiles[filename]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// createPackage builds the LLVM IR for all types, methods, and global variables
|
||||||
|
// in the given package.
|
||||||
|
func (c *compilerContext) createPackage(irbuilder llvm.Builder, pkg *ssa.Package) {
|
||||||
|
// Sort by position, so that the order of the functions in the IR matches
|
||||||
|
// the order of functions in the source file. This is useful for testing,
|
||||||
|
// for example.
|
||||||
|
var members []string
|
||||||
|
for name := range pkg.Members {
|
||||||
|
members = append(members, name)
|
||||||
|
}
|
||||||
|
sort.Slice(members, func(i, j int) bool {
|
||||||
|
iPos := pkg.Members[members[i]].Pos()
|
||||||
|
jPos := pkg.Members[members[j]].Pos()
|
||||||
|
if i == j {
|
||||||
|
// Cannot sort by pos, so do it by name.
|
||||||
|
return members[i] < members[j]
|
||||||
|
}
|
||||||
|
return iPos < jPos
|
||||||
|
})
|
||||||
|
|
||||||
|
// Define all functions.
|
||||||
|
for _, name := range members {
|
||||||
|
member := pkg.Members[name]
|
||||||
|
switch member := member.(type) {
|
||||||
|
case *ssa.Function:
|
||||||
|
if member.Blocks == nil {
|
||||||
|
continue // external function
|
||||||
|
}
|
||||||
|
// Create the function definition.
|
||||||
|
b := newBuilder(c, irbuilder, member)
|
||||||
|
b.createFunction()
|
||||||
|
case *ssa.Type:
|
||||||
|
if types.IsInterface(member.Type()) {
|
||||||
|
// Interfaces don't have concrete methods.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Named type. We should make sure all methods are created.
|
||||||
|
// This includes both functions with pointer receivers and those
|
||||||
|
// without.
|
||||||
|
methods := getAllMethods(pkg.Prog, member.Type())
|
||||||
|
methods = append(methods, getAllMethods(pkg.Prog, types.NewPointer(member.Type()))...)
|
||||||
|
for _, method := range methods {
|
||||||
|
// Parse this method.
|
||||||
|
fn := pkg.Prog.MethodValue(method)
|
||||||
|
if fn.Blocks == nil {
|
||||||
|
continue // external function
|
||||||
|
}
|
||||||
|
if member.Type().String() != member.String() {
|
||||||
|
// This is a member on a type alias. Do not build such a
|
||||||
|
// function.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if fn.Synthetic != "" && fn.Synthetic != "package initializer" {
|
||||||
|
// This function is a kind of wrapper function (created by
|
||||||
|
// the ssa package, not appearing in the source code) that
|
||||||
|
// is created by the getFunction method as needed.
|
||||||
|
// Therefore, don't build it here to avoid "function
|
||||||
|
// redeclared" errors.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Create the function definition.
|
||||||
|
b := newBuilder(c, irbuilder, fn)
|
||||||
|
b.createFunction()
|
||||||
|
}
|
||||||
|
case *ssa.Global:
|
||||||
|
// Global variable.
|
||||||
|
info := c.getGlobalInfo(member)
|
||||||
|
if !info.extern {
|
||||||
|
global := c.getGlobal(member)
|
||||||
|
global.SetInitializer(llvm.ConstNull(global.Type().ElementType()))
|
||||||
|
global.SetLinkage(llvm.InternalLinkage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// createFunction builds the LLVM IR implementation for this function. The
|
// createFunction builds the LLVM IR implementation for this function. The
|
||||||
// function must not yet be defined, otherwise this function will create a
|
// function must not yet be defined, otherwise this function will create a
|
||||||
// diagnostic.
|
// diagnostic.
|
||||||
|
@ -767,7 +806,7 @@ func (b *builder) createFunction() {
|
||||||
fmt.Printf("\nfunc %s:\n", b.fn)
|
fmt.Printf("\nfunc %s:\n", b.fn)
|
||||||
}
|
}
|
||||||
if !b.llvmFn.IsDeclaration() {
|
if !b.llvmFn.IsDeclaration() {
|
||||||
errValue := b.fn.Name() + " redeclared in this program"
|
errValue := b.llvmFn.Name() + " redeclared in this program"
|
||||||
fnPos := getPosition(b.llvmFn)
|
fnPos := getPosition(b.llvmFn)
|
||||||
if fnPos.IsValid() {
|
if fnPos.IsValid() {
|
||||||
errValue += "\n\tprevious declaration at " + fnPos.String()
|
errValue += "\n\tprevious declaration at " + fnPos.String()
|
||||||
|
@ -948,6 +987,12 @@ func (b *builder) createFunction() {
|
||||||
b.trackValue(phi.llvm)
|
b.trackValue(phi.llvm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create anonymous functions (closures etc.).
|
||||||
|
for _, sub := range b.fn.AnonFuncs {
|
||||||
|
b := newBuilder(b.compilerContext, b.Builder, sub)
|
||||||
|
b.createFunction()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// createInstruction builds the LLVM IR equivalent instructions for the
|
// createInstruction builds the LLVM IR equivalent instructions for the
|
||||||
|
|
|
@ -1,164 +0,0 @@
|
||||||
package compiler
|
|
||||||
|
|
||||||
// This file implements a simple reachability analysis, to reduce compile time.
|
|
||||||
// This DCE pass used to be necessary for improving other passes but now it
|
|
||||||
// isn't necessary anymore.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"go/types"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/loader"
|
|
||||||
"golang.org/x/tools/go/ssa"
|
|
||||||
)
|
|
||||||
|
|
||||||
type dceState struct {
|
|
||||||
*compilerContext
|
|
||||||
functions []*dceFunction
|
|
||||||
functionMap map[*ssa.Function]*dceFunction
|
|
||||||
}
|
|
||||||
|
|
||||||
type dceFunction struct {
|
|
||||||
*ssa.Function
|
|
||||||
functionInfo
|
|
||||||
flag bool // used by dead code elimination
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *dceState) addFunction(ssaFn *ssa.Function) {
|
|
||||||
if _, ok := p.functionMap[ssaFn]; ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
f := &dceFunction{Function: ssaFn}
|
|
||||||
f.functionInfo = p.getFunctionInfo(ssaFn)
|
|
||||||
p.functions = append(p.functions, f)
|
|
||||||
p.functionMap[ssaFn] = f
|
|
||||||
|
|
||||||
for _, anon := range ssaFn.AnonFuncs {
|
|
||||||
p.addFunction(anon)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// simpleDCE returns a list of alive functions in the program. Compiling only
|
|
||||||
// these functions makes the compiler faster.
|
|
||||||
//
|
|
||||||
// This functionality will likely be replaced in the future with build caching.
|
|
||||||
func (c *compilerContext) simpleDCE(lprogram *loader.Program) ([]*ssa.Function, error) {
|
|
||||||
mainPkg := c.program.Package(lprogram.MainPkg().Pkg)
|
|
||||||
if mainPkg == nil {
|
|
||||||
panic("could not find main package")
|
|
||||||
}
|
|
||||||
p := &dceState{
|
|
||||||
compilerContext: c,
|
|
||||||
functionMap: make(map[*ssa.Function]*dceFunction),
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, pkg := range lprogram.Sorted() {
|
|
||||||
pkg := c.program.Package(pkg.Pkg)
|
|
||||||
memberNames := make([]string, 0)
|
|
||||||
for name := range pkg.Members {
|
|
||||||
memberNames = append(memberNames, name)
|
|
||||||
}
|
|
||||||
sort.Strings(memberNames)
|
|
||||||
|
|
||||||
for _, name := range memberNames {
|
|
||||||
member := pkg.Members[name]
|
|
||||||
switch member := member.(type) {
|
|
||||||
case *ssa.Function:
|
|
||||||
p.addFunction(member)
|
|
||||||
case *ssa.Type:
|
|
||||||
methods := getAllMethods(pkg.Prog, member.Type())
|
|
||||||
if !types.IsInterface(member.Type()) {
|
|
||||||
// named type
|
|
||||||
for _, method := range methods {
|
|
||||||
p.addFunction(pkg.Prog.MethodValue(method))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case *ssa.Global:
|
|
||||||
// Ignore. Globals are not handled here.
|
|
||||||
case *ssa.NamedConst:
|
|
||||||
// Ignore: these are already resolved.
|
|
||||||
default:
|
|
||||||
panic("unknown member type: " + member.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initial set of live functions. Include main.main, *.init and runtime.*
|
|
||||||
// functions.
|
|
||||||
main, ok := mainPkg.Members["main"].(*ssa.Function)
|
|
||||||
if !ok {
|
|
||||||
if mainPkg.Members["main"] == nil {
|
|
||||||
return nil, errors.New("function main is undeclared in the main package")
|
|
||||||
} else {
|
|
||||||
return nil, errors.New("cannot declare main - must be func")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
runtimePkg := c.program.ImportedPackage("runtime")
|
|
||||||
mathPkg := c.program.ImportedPackage("math")
|
|
||||||
taskPkg := c.program.ImportedPackage("internal/task")
|
|
||||||
p.functionMap[main].flag = true
|
|
||||||
worklist := []*ssa.Function{main}
|
|
||||||
for _, f := range p.functions {
|
|
||||||
if f.exported || f.Synthetic == "package initializer" || f.Pkg == runtimePkg || f.Pkg == taskPkg || (f.Pkg == mathPkg && f.Pkg != nil) {
|
|
||||||
if f.flag {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
f.flag = true
|
|
||||||
worklist = append(worklist, f.Function)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark all called functions recursively.
|
|
||||||
for len(worklist) != 0 {
|
|
||||||
f := worklist[len(worklist)-1]
|
|
||||||
worklist = worklist[:len(worklist)-1]
|
|
||||||
for _, block := range f.Blocks {
|
|
||||||
for _, instr := range block.Instrs {
|
|
||||||
if instr, ok := instr.(*ssa.MakeInterface); ok {
|
|
||||||
for _, sel := range getAllMethods(c.program, instr.X.Type()) {
|
|
||||||
fn := c.program.MethodValue(sel)
|
|
||||||
callee := p.functionMap[fn]
|
|
||||||
if callee == nil {
|
|
||||||
// TODO: why is this necessary?
|
|
||||||
p.addFunction(fn)
|
|
||||||
callee = p.functionMap[fn]
|
|
||||||
}
|
|
||||||
if !callee.flag {
|
|
||||||
callee.flag = true
|
|
||||||
worklist = append(worklist, callee.Function)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, operand := range instr.Operands(nil) {
|
|
||||||
if operand == nil || *operand == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch operand := (*operand).(type) {
|
|
||||||
case *ssa.Function:
|
|
||||||
f := p.functionMap[operand]
|
|
||||||
if f == nil {
|
|
||||||
// FIXME HACK: this function should have been
|
|
||||||
// discovered already. It is not for bound methods.
|
|
||||||
p.addFunction(operand)
|
|
||||||
f = p.functionMap[operand]
|
|
||||||
}
|
|
||||||
if !f.flag {
|
|
||||||
f.flag = true
|
|
||||||
worklist = append(worklist, operand)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return all live functions.
|
|
||||||
liveFunctions := []*ssa.Function{}
|
|
||||||
for _, f := range p.functions {
|
|
||||||
if f.flag {
|
|
||||||
liveFunctions = append(liveFunctions, f.Function)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return liveFunctions, nil
|
|
||||||
}
|
|
|
@ -172,6 +172,21 @@ func (c *compilerContext) getFunction(fn *ssa.Function) llvm.Value {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Synthetic functions are functions that do not appear in the source code,
|
||||||
|
// they are artificially constructed. Usually they are wrapper functions
|
||||||
|
// that are not referenced anywhere except in a SSA call instruction so
|
||||||
|
// should be created right away.
|
||||||
|
// The exception is the package initializer, which does appear in the
|
||||||
|
// *ssa.Package members and so shouldn't be created here.
|
||||||
|
if fn.Synthetic != "" && fn.Synthetic != "package initializer" {
|
||||||
|
irbuilder := c.ctx.NewBuilder()
|
||||||
|
b := newBuilder(c, irbuilder, fn)
|
||||||
|
b.createFunction()
|
||||||
|
irbuilder.Dispose()
|
||||||
|
llvmFn.SetLinkage(llvm.InternalLinkage)
|
||||||
|
llvmFn.SetUnnamedAddr(true)
|
||||||
|
}
|
||||||
|
|
||||||
return llvmFn
|
return llvmFn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,10 +304,8 @@ type globalInfo struct {
|
||||||
|
|
||||||
// loadASTComments loads comments on globals from the AST, for use later in the
|
// loadASTComments loads comments on globals from the AST, for use later in the
|
||||||
// program. In particular, they are required for //go:extern pragmas on globals.
|
// program. In particular, they are required for //go:extern pragmas on globals.
|
||||||
func (c *compilerContext) loadASTComments(lprogram *loader.Program) {
|
func (c *compilerContext) loadASTComments(pkg *loader.Package) {
|
||||||
c.astComments = map[string]*ast.CommentGroup{}
|
for _, file := range pkg.Files {
|
||||||
for _, pkgInfo := range lprogram.Sorted() {
|
|
||||||
for _, file := range pkgInfo.Files {
|
|
||||||
for _, decl := range file.Decls {
|
for _, decl := range file.Decls {
|
||||||
switch decl := decl.(type) {
|
switch decl := decl.(type) {
|
||||||
case *ast.GenDecl:
|
case *ast.GenDecl:
|
||||||
|
@ -305,7 +318,7 @@ func (c *compilerContext) loadASTComments(lprogram *loader.Program) {
|
||||||
switch spec := spec.(type) {
|
switch spec := spec.(type) {
|
||||||
case *ast.ValueSpec: // decl.Tok == token.VAR
|
case *ast.ValueSpec: // decl.Tok == token.VAR
|
||||||
for _, name := range spec.Names {
|
for _, name := range spec.Names {
|
||||||
id := pkgInfo.Pkg.Path() + "." + name.Name
|
id := pkg.Pkg.Path() + "." + name.Name
|
||||||
c.astComments[id] = decl.Doc
|
c.astComments[id] = decl.Doc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -314,7 +327,6 @@ func (c *compilerContext) loadASTComments(lprogram *loader.Program) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getGlobal returns a LLVM IR global value for a Go SSA global. It is added to
|
// getGlobal returns a LLVM IR global value for a Go SSA global. It is added to
|
||||||
|
@ -326,10 +338,6 @@ func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
|
||||||
typ := g.Type().(*types.Pointer).Elem()
|
typ := g.Type().(*types.Pointer).Elem()
|
||||||
llvmType := c.getLLVMType(typ)
|
llvmType := c.getLLVMType(typ)
|
||||||
llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName)
|
llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName)
|
||||||
if !info.extern {
|
|
||||||
llvmGlobal.SetInitializer(llvm.ConstNull(llvmType))
|
|
||||||
llvmGlobal.SetLinkage(llvm.InternalLinkage)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set alignment from the //go:align comment.
|
// Set alignment from the //go:align comment.
|
||||||
var alignInBits uint32
|
var alignInBits uint32
|
||||||
|
@ -347,8 +355,6 @@ func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
|
||||||
|
|
||||||
if c.Debug && !info.extern {
|
if c.Debug && !info.extern {
|
||||||
// Add debug info.
|
// Add debug info.
|
||||||
// TODO: this should be done for every global in the program, not just
|
|
||||||
// the ones that are referenced from some code.
|
|
||||||
pos := c.program.Fset.Position(g.Pos())
|
pos := c.program.Fset.Position(g.Pos())
|
||||||
diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{
|
diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{
|
||||||
Name: g.RelString(nil),
|
Name: g.RelString(nil),
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче