all: use the new LLVM pass manager
The old LLVM pass manager is deprecated and should not be used anymore. Moreover, the pass manager builder (which we used to set up a pass pipeline) is actually removed from LLVM entirely in LLVM 17: https://reviews.llvm.org/D145387 https://reviews.llvm.org/D145835 The new pass manager does change the binary size in many cases: both growing and shrinking it. However, on average the binary size remains more or less the same. This is needed as a preparation for LLVM 17.
Этот коммит содержится в:
родитель
1da1abe314
коммит
3b1913ac57
10 изменённых файлов: 76 добавлений и 118 удалений
|
@ -83,8 +83,7 @@ type packageAction struct {
|
||||||
FileHashes map[string]string // hash of every file that's part of the package
|
FileHashes map[string]string // hash of every file that's part of the package
|
||||||
EmbeddedFiles map[string]string // hash of all the //go:embed files in the package
|
EmbeddedFiles map[string]string // hash of all the //go:embed files in the package
|
||||||
Imports map[string]string // map from imported package to action ID hash
|
Imports map[string]string // map from imported package to action ID hash
|
||||||
OptLevel int // LLVM optimization level (0-3)
|
OptLevel string // LLVM optimization level (O0, O1, O2, Os, Oz)
|
||||||
SizeLevel int // LLVM optimization for size level (0-2)
|
|
||||||
UndefinedGlobals []string // globals that are left as external globals (no initializer)
|
UndefinedGlobals []string // globals that are left as external globals (no initializer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,7 +157,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||||
return BuildResult{}, fmt.Errorf("unknown libc: %s", config.Target.Libc)
|
return BuildResult{}, fmt.Errorf("unknown libc: %s", config.Target.Libc)
|
||||||
}
|
}
|
||||||
|
|
||||||
optLevel, sizeLevel, _ := config.OptLevels()
|
optLevel, speedLevel, sizeLevel := config.OptLevel()
|
||||||
compilerConfig := &compiler.Config{
|
compilerConfig := &compiler.Config{
|
||||||
Triple: config.Triple(),
|
Triple: config.Triple(),
|
||||||
CPU: config.CPU(),
|
CPU: config.CPU(),
|
||||||
|
@ -321,7 +320,6 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||||
EmbeddedFiles: make(map[string]string, len(allFiles)),
|
EmbeddedFiles: make(map[string]string, len(allFiles)),
|
||||||
Imports: make(map[string]string, len(pkg.Pkg.Imports())),
|
Imports: make(map[string]string, len(pkg.Pkg.Imports())),
|
||||||
OptLevel: optLevel,
|
OptLevel: optLevel,
|
||||||
SizeLevel: sizeLevel,
|
|
||||||
UndefinedGlobals: undefinedGlobals,
|
UndefinedGlobals: undefinedGlobals,
|
||||||
}
|
}
|
||||||
for filePath, hash := range pkg.FileHashes {
|
for filePath, hash := range pkg.FileHashes {
|
||||||
|
@ -743,17 +741,17 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||||
if config.GOOS() == "windows" {
|
if config.GOOS() == "windows" {
|
||||||
// Options for the MinGW wrapper for the lld COFF linker.
|
// Options for the MinGW wrapper for the lld COFF linker.
|
||||||
ldflags = append(ldflags,
|
ldflags = append(ldflags,
|
||||||
"-Xlink=/opt:lldlto="+strconv.Itoa(optLevel),
|
"-Xlink=/opt:lldlto="+strconv.Itoa(speedLevel),
|
||||||
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"))
|
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"))
|
||||||
} else if config.GOOS() == "darwin" {
|
} else if config.GOOS() == "darwin" {
|
||||||
// Options for the ld64-compatible lld linker.
|
// Options for the ld64-compatible lld linker.
|
||||||
ldflags = append(ldflags,
|
ldflags = append(ldflags,
|
||||||
"--lto-O"+strconv.Itoa(optLevel),
|
"--lto-O"+strconv.Itoa(speedLevel),
|
||||||
"-cache_path_lto", filepath.Join(cacheDir, "thinlto"))
|
"-cache_path_lto", filepath.Join(cacheDir, "thinlto"))
|
||||||
} else {
|
} else {
|
||||||
// Options for the ELF linker.
|
// Options for the ELF linker.
|
||||||
ldflags = append(ldflags,
|
ldflags = append(ldflags,
|
||||||
"--lto-O"+strconv.Itoa(optLevel),
|
"--lto-O"+strconv.Itoa(speedLevel),
|
||||||
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"),
|
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1066,10 +1064,9 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optimization levels here are roughly the same as Clang, but probably not
|
// Run most of the whole-program optimizations (including the whole
|
||||||
// exactly.
|
// O0/O1/O2/Os/Oz optimization pipeline).
|
||||||
optLevel, sizeLevel, inlinerThreshold := config.OptLevels()
|
errs := transform.Optimize(mod, config)
|
||||||
errs := transform.Optimize(mod, config, optLevel, sizeLevel, inlinerThreshold)
|
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
return newMultiError(errs)
|
return newMultiError(errs)
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,9 +41,9 @@ func TestBinarySize(t *testing.T) {
|
||||||
// This is a small number of very diverse targets that we want to test.
|
// This is a small number of very diverse targets that we want to test.
|
||||||
tests := []sizeTest{
|
tests := []sizeTest{
|
||||||
// microcontrollers
|
// microcontrollers
|
||||||
{"hifive1b", "examples/echo", 4568, 280, 0, 2252},
|
{"hifive1b", "examples/echo", 4484, 280, 0, 2252},
|
||||||
{"microbit", "examples/serial", 2728, 388, 8, 2256},
|
{"microbit", "examples/serial", 2724, 388, 8, 2256},
|
||||||
{"wioterminal", "examples/pininterrupt", 5996, 1484, 116, 6816},
|
{"wioterminal", "examples/pininterrupt", 6000, 1484, 116, 6816},
|
||||||
|
|
||||||
// TODO: also check wasm. Right now this is difficult, because
|
// TODO: also check wasm. Right now this is difficult, because
|
||||||
// wasm binaries are run through wasm-opt and therefore the
|
// wasm binaries are run through wasm-opt and therefore the
|
||||||
|
|
|
@ -145,18 +145,18 @@ func (c *Config) Serial() string {
|
||||||
|
|
||||||
// OptLevels returns the optimization level (0-2), size level (0-2), and inliner
|
// OptLevels returns the optimization level (0-2), size level (0-2), and inliner
|
||||||
// threshold as used in the LLVM optimization pipeline.
|
// threshold as used in the LLVM optimization pipeline.
|
||||||
func (c *Config) OptLevels() (optLevel, sizeLevel int, inlinerThreshold uint) {
|
func (c *Config) OptLevel() (level string, speedLevel, sizeLevel int) {
|
||||||
switch c.Options.Opt {
|
switch c.Options.Opt {
|
||||||
case "none", "0":
|
case "none", "0":
|
||||||
return 0, 0, 0 // -O0
|
return "O0", 0, 0
|
||||||
case "1":
|
case "1":
|
||||||
return 1, 0, 0 // -O1
|
return "O1", 1, 0
|
||||||
case "2":
|
case "2":
|
||||||
return 2, 0, 225 // -O2
|
return "O2", 2, 0
|
||||||
case "s":
|
case "s":
|
||||||
return 2, 1, 225 // -Os
|
return "Os", 2, 1
|
||||||
case "z":
|
case "z":
|
||||||
return 2, 2, 5 // -Oz, default
|
return "Oz", 2, 2 // default
|
||||||
default:
|
default:
|
||||||
// This is not shown to the user: valid choices are already checked as
|
// This is not shown to the user: valid choices are already checked as
|
||||||
// part of Options.Verify(). It is here as a sanity check.
|
// part of Options.Verify(). It is here as a sanity check.
|
||||||
|
|
|
@ -91,14 +91,12 @@ func TestCompiler(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optimize IR a little.
|
// Optimize IR a little.
|
||||||
funcPasses := llvm.NewFunctionPassManagerForModule(mod)
|
passOptions := llvm.NewPassBuilderOptions()
|
||||||
defer funcPasses.Dispose()
|
defer passOptions.Dispose()
|
||||||
funcPasses.AddInstructionCombiningPass()
|
err = mod.RunPasses("instcombine", llvm.TargetMachine{}, passOptions)
|
||||||
funcPasses.InitializeFunc()
|
if err != nil {
|
||||||
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
t.Error(err)
|
||||||
funcPasses.RunFunc(fn)
|
|
||||||
}
|
}
|
||||||
funcPasses.FinalizeFunc()
|
|
||||||
|
|
||||||
outFilePrefix := tc.file[:len(tc.file)-3]
|
outFilePrefix := tc.file[:len(tc.file)-3]
|
||||||
if tc.target != "" {
|
if tc.target != "" {
|
||||||
|
|
|
@ -77,12 +77,9 @@ func runTest(t *testing.T, pathPrefix string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run some cleanup passes to get easy-to-read outputs.
|
// Run some cleanup passes to get easy-to-read outputs.
|
||||||
pm := llvm.NewPassManager()
|
to := llvm.NewPassBuilderOptions()
|
||||||
defer pm.Dispose()
|
defer to.Dispose()
|
||||||
pm.AddGlobalOptimizerPass()
|
mod.RunPasses("globalopt,dse,adce", llvm.TargetMachine{}, to)
|
||||||
pm.AddDeadStoreEliminationPass()
|
|
||||||
pm.AddAggressiveDCEPass()
|
|
||||||
pm.Run(mod)
|
|
||||||
|
|
||||||
// Read the expected output IR.
|
// Read the expected output IR.
|
||||||
out, err := os.ReadFile(pathPrefix + ".out.ll")
|
out, err := os.ReadFile(pathPrefix + ".out.ll")
|
||||||
|
|
|
@ -38,11 +38,12 @@ func TestAllocs2(t *testing.T) {
|
||||||
mod := compileGoFileForTesting(t, "./testdata/allocs2.go")
|
mod := compileGoFileForTesting(t, "./testdata/allocs2.go")
|
||||||
|
|
||||||
// Run functionattrs pass, which is necessary for escape analysis.
|
// Run functionattrs pass, which is necessary for escape analysis.
|
||||||
pm := llvm.NewPassManager()
|
po := llvm.NewPassBuilderOptions()
|
||||||
defer pm.Dispose()
|
defer po.Dispose()
|
||||||
pm.AddInstructionCombiningPass()
|
err := mod.RunPasses("function(instcombine),function-attrs", llvm.TargetMachine{}, po)
|
||||||
pm.AddFunctionAttrsPass()
|
if err != nil {
|
||||||
pm.Run(mod)
|
t.Error("failed to run passes:", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Run heap to stack transform.
|
// Run heap to stack transform.
|
||||||
var testOutputs []allocsTestOutput
|
var testOutputs []allocsTestOutput
|
||||||
|
|
|
@ -15,9 +15,11 @@ func TestInterfaceLowering(t *testing.T) {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pm := llvm.NewPassManager()
|
po := llvm.NewPassBuilderOptions()
|
||||||
defer pm.Dispose()
|
defer po.Dispose()
|
||||||
pm.AddGlobalDCEPass()
|
err = mod.RunPasses("globaldce", llvm.TargetMachine{}, po)
|
||||||
pm.Run(mod)
|
if err != nil {
|
||||||
|
t.Error("failed to run passes:", err)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,10 +15,11 @@ func TestOptimizeMaps(t *testing.T) {
|
||||||
|
|
||||||
// Run an optimization pass, to clean up the result.
|
// Run an optimization pass, to clean up the result.
|
||||||
// This shows that all code related to the map is really eliminated.
|
// This shows that all code related to the map is really eliminated.
|
||||||
pm := llvm.NewPassManager()
|
po := llvm.NewPassBuilderOptions()
|
||||||
defer pm.Dispose()
|
defer po.Dispose()
|
||||||
pm.AddDeadStoreEliminationPass()
|
err := mod.RunPasses("dse,adce", llvm.TargetMachine{}, po)
|
||||||
pm.AddAggressiveDCEPass()
|
if err != nil {
|
||||||
pm.Run(mod)
|
t.Error("failed to run passes:", err)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,54 +14,22 @@ import (
|
||||||
// OptimizePackage runs optimization passes over the LLVM module for the given
|
// OptimizePackage runs optimization passes over the LLVM module for the given
|
||||||
// Go package.
|
// Go package.
|
||||||
func OptimizePackage(mod llvm.Module, config *compileopts.Config) {
|
func OptimizePackage(mod llvm.Module, config *compileopts.Config) {
|
||||||
optLevel, sizeLevel, _ := config.OptLevels()
|
_, speedLevel, _ := config.OptLevel()
|
||||||
|
|
||||||
// Run function passes for each function in the module.
|
|
||||||
// These passes are intended to be run on each function right
|
|
||||||
// after they're created to reduce IR size (and maybe also for
|
|
||||||
// cache locality to improve performance), but for now they're
|
|
||||||
// run here for each function in turn. Maybe this can be
|
|
||||||
// improved in the future.
|
|
||||||
builder := llvm.NewPassManagerBuilder()
|
|
||||||
defer builder.Dispose()
|
|
||||||
builder.SetOptLevel(optLevel)
|
|
||||||
builder.SetSizeLevel(sizeLevel)
|
|
||||||
funcPasses := llvm.NewFunctionPassManagerForModule(mod)
|
|
||||||
defer funcPasses.Dispose()
|
|
||||||
builder.PopulateFunc(funcPasses)
|
|
||||||
funcPasses.InitializeFunc()
|
|
||||||
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
|
||||||
if fn.IsDeclaration() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
funcPasses.RunFunc(fn)
|
|
||||||
}
|
|
||||||
funcPasses.FinalizeFunc()
|
|
||||||
|
|
||||||
// Run TinyGo-specific optimization passes.
|
// Run TinyGo-specific optimization passes.
|
||||||
if optLevel > 0 {
|
if speedLevel > 0 {
|
||||||
OptimizeMaps(mod)
|
OptimizeMaps(mod)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optimize runs a number of optimization and transformation passes over the
|
// Optimize runs a number of optimization and transformation passes over the
|
||||||
// given module. Some passes are specific to TinyGo, others are generic LLVM
|
// given module. Some passes are specific to TinyGo, others are generic LLVM
|
||||||
// passes. You can set a preferred performance (0-3) and size (0-2) level and
|
// passes.
|
||||||
// control the limits of the inliner (higher numbers mean more inlining, set it
|
|
||||||
// to 0 to disable entirely).
|
|
||||||
//
|
//
|
||||||
// Please note that some optimizations are not optional, thus Optimize must
|
// Please note that some optimizations are not optional, thus Optimize must
|
||||||
// alwasy be run before emitting machine code. Set all controls (optLevel,
|
// alwasy be run before emitting machine code.
|
||||||
// sizeLevel, inlinerThreshold) to 0 to reduce the number of optimizations to a
|
func Optimize(mod llvm.Module, config *compileopts.Config) []error {
|
||||||
// minimum.
|
optLevel, speedLevel, _ := config.OptLevel()
|
||||||
func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel int, inlinerThreshold uint) []error {
|
|
||||||
builder := llvm.NewPassManagerBuilder()
|
|
||||||
defer builder.Dispose()
|
|
||||||
builder.SetOptLevel(optLevel)
|
|
||||||
builder.SetSizeLevel(sizeLevel)
|
|
||||||
if inlinerThreshold != 0 {
|
|
||||||
builder.UseInlinerWithThreshold(inlinerThreshold)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure these functions are kept in tact during TinyGo transformation passes.
|
// Make sure these functions are kept in tact during TinyGo transformation passes.
|
||||||
for _, name := range functionsUsedInTransforms {
|
for _, name := range functionsUsedInTransforms {
|
||||||
|
@ -84,23 +52,20 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if optLevel > 0 {
|
if speedLevel > 0 {
|
||||||
// Run some preparatory passes for the Go optimizer.
|
// Run some preparatory passes for the Go optimizer.
|
||||||
goPasses := llvm.NewPassManager()
|
po := llvm.NewPassBuilderOptions()
|
||||||
defer goPasses.Dispose()
|
defer po.Dispose()
|
||||||
goPasses.AddGlobalDCEPass()
|
err := mod.RunPasses("globaldce,globalopt,ipsccp,instcombine,adce,function-attrs", llvm.TargetMachine{}, po)
|
||||||
goPasses.AddGlobalOptimizerPass()
|
if err != nil {
|
||||||
goPasses.AddIPSCCPPass()
|
return []error{fmt.Errorf("could not build pass pipeline: %w", err)}
|
||||||
goPasses.AddInstructionCombiningPass() // necessary for OptimizeReflectImplements
|
}
|
||||||
goPasses.AddAggressiveDCEPass()
|
|
||||||
goPasses.AddFunctionAttrsPass()
|
|
||||||
goPasses.Run(mod)
|
|
||||||
|
|
||||||
// Run TinyGo-specific optimization passes.
|
// Run TinyGo-specific optimization passes.
|
||||||
OptimizeStringToBytes(mod)
|
OptimizeStringToBytes(mod)
|
||||||
OptimizeReflectImplements(mod)
|
OptimizeReflectImplements(mod)
|
||||||
OptimizeAllocs(mod, nil, nil)
|
OptimizeAllocs(mod, nil, nil)
|
||||||
err := LowerInterfaces(mod, config)
|
err = LowerInterfaces(mod, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []error{err}
|
return []error{err}
|
||||||
}
|
}
|
||||||
|
@ -113,7 +78,10 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i
|
||||||
// After interfaces are lowered, there are many more opportunities for
|
// After interfaces are lowered, there are many more opportunities for
|
||||||
// interprocedural optimizations. To get them to work, function
|
// interprocedural optimizations. To get them to work, function
|
||||||
// attributes have to be updated first.
|
// attributes have to be updated first.
|
||||||
goPasses.Run(mod)
|
err = mod.RunPasses("globaldce,globalopt,ipsccp,instcombine,adce,function-attrs", llvm.TargetMachine{}, po)
|
||||||
|
if err != nil {
|
||||||
|
return []error{fmt.Errorf("could not build pass pipeline: %w", err)}
|
||||||
|
}
|
||||||
|
|
||||||
// Run TinyGo-specific interprocedural optimizations.
|
// Run TinyGo-specific interprocedural optimizations.
|
||||||
OptimizeAllocs(mod, config.Options.PrintAllocs, func(pos token.Position, msg string) {
|
OptimizeAllocs(mod, config.Options.PrintAllocs, func(pos token.Position, msg string) {
|
||||||
|
@ -134,10 +102,12 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up some leftover symbols of the previous transformations.
|
// Clean up some leftover symbols of the previous transformations.
|
||||||
goPasses := llvm.NewPassManager()
|
po := llvm.NewPassBuilderOptions()
|
||||||
defer goPasses.Dispose()
|
defer po.Dispose()
|
||||||
goPasses.AddGlobalDCEPass()
|
err = mod.RunPasses("globaldce", llvm.TargetMachine{}, po)
|
||||||
goPasses.Run(mod)
|
if err != nil {
|
||||||
|
return []error{fmt.Errorf("could not build pass pipeline: %w", err)}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Scheduler() == "none" {
|
if config.Scheduler() == "none" {
|
||||||
|
@ -169,23 +139,15 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i
|
||||||
fn.SetLinkage(llvm.InternalLinkage)
|
fn.SetLinkage(llvm.InternalLinkage)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run function passes again, because without it, llvm.coro.size.i32()
|
// Run the default pass pipeline.
|
||||||
// doesn't get lowered.
|
// TODO: set the PrepareForThinLTO flag somehow.
|
||||||
funcPasses := llvm.NewFunctionPassManagerForModule(mod)
|
po := llvm.NewPassBuilderOptions()
|
||||||
defer funcPasses.Dispose()
|
defer po.Dispose()
|
||||||
builder.PopulateFunc(funcPasses)
|
passes := fmt.Sprintf("default<%s>", optLevel)
|
||||||
funcPasses.InitializeFunc()
|
err := mod.RunPasses(passes, llvm.TargetMachine{}, po)
|
||||||
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
if err != nil {
|
||||||
funcPasses.RunFunc(fn)
|
return []error{fmt.Errorf("could not build pass pipeline: %w", err)}
|
||||||
}
|
}
|
||||||
funcPasses.FinalizeFunc()
|
|
||||||
|
|
||||||
// Run module passes.
|
|
||||||
// TODO: somehow set the PrepareForThinLTO flag in the pass manager builder.
|
|
||||||
modPasses := llvm.NewPassManager()
|
|
||||||
defer modPasses.Dispose()
|
|
||||||
builder.Populate(modPasses)
|
|
||||||
modPasses.Run(mod)
|
|
||||||
|
|
||||||
hasGCPass := MakeGCStackSlots(mod)
|
hasGCPass := MakeGCStackSlots(mod)
|
||||||
if hasGCPass {
|
if hasGCPass {
|
||||||
|
|
|
@ -22,7 +22,7 @@ import (
|
||||||
// the -opt= compiler flag.
|
// the -opt= compiler flag.
|
||||||
func AddStandardAttributes(fn llvm.Value, config *compileopts.Config) {
|
func AddStandardAttributes(fn llvm.Value, config *compileopts.Config) {
|
||||||
ctx := fn.Type().Context()
|
ctx := fn.Type().Context()
|
||||||
_, sizeLevel, _ := config.OptLevels()
|
_, _, sizeLevel := config.OptLevel()
|
||||||
if sizeLevel >= 1 {
|
if sizeLevel >= 1 {
|
||||||
fn.AddFunctionAttr(ctx.CreateEnumAttribute(llvm.AttributeKindID("optsize"), 0))
|
fn.AddFunctionAttr(ctx.CreateEnumAttribute(llvm.AttributeKindID("optsize"), 0))
|
||||||
}
|
}
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче