diff --git a/0001-Skip-WASI-tests.patch b/0001-Skip-WASI-tests.patch index 70040f2..63bc8d7 100644 --- a/0001-Skip-WASI-tests.patch +++ b/0001-Skip-WASI-tests.patch @@ -1,7 +1,7 @@ From ee856392109e39cf60e83022441c94adee205447 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 15 Dec 2020 05:06:04 -0500 -Subject: [PATCH 1/3] Skip WASI tests. +Subject: [PATCH 1/7] Skip WASI tests. We do not have wasmtime available. diff --git a/0002-Skip-some-cross-Linux-tests-where-qemu-is-broken.patch b/0002-Skip-some-cross-Linux-tests-where-qemu-is-broken.patch index 5c12aca..79deaa6 100644 --- a/0002-Skip-some-cross-Linux-tests-where-qemu-is-broken.patch +++ b/0002-Skip-some-cross-Linux-tests-where-qemu-is-broken.patch @@ -1,7 +1,7 @@ From 1f60829a1fffdcbe268c1998a560fdeb6029407e Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 3 Jan 2022 22:39:31 -0500 -Subject: [PATCH 2/3] Skip some cross Linux tests where qemu is broken +Subject: [PATCH 2/7] Skip some cross Linux tests where qemu is broken The upstream issues will hopefully be fixed soon: diff --git a/0003-Suggest-optional-packages-to-install-if-missing.patch b/0003-Suggest-optional-packages-to-install-if-missing.patch index 95e0413..c09cc88 100644 --- a/0003-Suggest-optional-packages-to-install-if-missing.patch +++ b/0003-Suggest-optional-packages-to-install-if-missing.patch @@ -1,7 +1,7 @@ From 3f6a6f241ab37976f07e792986b5c47fdde20c8a Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sun, 6 Feb 2022 03:49:16 -0500 -Subject: [PATCH 3/3] Suggest optional packages to install if missing +Subject: [PATCH 3/7] Suggest optional packages to install if missing Signed-off-by: Elliott Sales de Andrade --- diff --git a/0004-transform-fix-bug-in-StringToBytes-optimization-pass.patch b/0004-transform-fix-bug-in-StringToBytes-optimization-pass.patch new file mode 100644 index 0000000..b0c42ec --- /dev/null +++ b/0004-transform-fix-bug-in-StringToBytes-optimization-pass.patch @@ -0,0 +1,121 @@ +From bf51015656ff3b1bbe6b41ea1155610178c2961d Mon Sep 17 00:00:00 2001 +From: Ayke van Laethem +Date: Thu, 21 Sep 2023 15:39:06 +0200 +Subject: [PATCH 4/7] transform: fix bug in StringToBytes optimization pass + +Previously, this pass would convert any read-only use of a +runtime.stringToBytes call to use the original string buffer instead. +This is incorrect: if there are any writes to the resulting buffer, none +of the slice buffer pointers can be converted to use the original +read-only string buffer. + +This commit fixes that bug and adds a test to prove the new (correct) +behavior. + +Signed-off-by: Elliott Sales de Andrade +--- + transform/rtcalls.go | 24 ++++++++++++++++++------ + transform/testdata/stringtobytes.ll | 16 ++++++++++++++++ + transform/testdata/stringtobytes.out.ll | 10 ++++++++++ + 3 files changed, 44 insertions(+), 6 deletions(-) + +diff --git a/transform/rtcalls.go b/transform/rtcalls.go +index 36d2853b..0b6feff2 100644 +--- a/transform/rtcalls.go ++++ b/transform/rtcalls.go +@@ -28,32 +28,44 @@ func OptimizeStringToBytes(mod llvm.Module) { + + // strptr is always constant because strings are always constant. + +- convertedAllUses := true ++ var pointerUses []llvm.Value ++ canConvertPointer := true + for _, use := range getUses(call) { + if use.IsAExtractValueInst().IsNil() { + // Expected an extractvalue, but this is something else. +- convertedAllUses = false ++ canConvertPointer = false + continue + } + switch use.Type().TypeKind() { + case llvm.IntegerTypeKind: + // A length (len or cap). Propagate the length value. ++ // This can always be done because the byte slice is always the ++ // same length as the original string. + use.ReplaceAllUsesWith(strlen) + use.EraseFromParentAsInstruction() + case llvm.PointerTypeKind: + // The string pointer itself. + if !isReadOnly(use) { +- convertedAllUses = false ++ // There is a store to the byte slice. This means that none ++ // of the pointer uses can't be propagated. ++ canConvertPointer = false + continue + } +- use.ReplaceAllUsesWith(strptr) +- use.EraseFromParentAsInstruction() ++ // It may be that the pointer value can be propagated, if all of ++ // the pointer uses are readonly. ++ pointerUses = append(pointerUses, use) + default: + // should not happen + panic("unknown return type of runtime.stringToBytes: " + use.Type().String()) + } + } +- if convertedAllUses { ++ if canConvertPointer { ++ // All pointer uses are readonly, so they can be converted. ++ for _, use := range pointerUses { ++ use.ReplaceAllUsesWith(strptr) ++ use.EraseFromParentAsInstruction() ++ } ++ + // Call to runtime.stringToBytes can be eliminated: both the input + // and the output is constant. + call.EraseFromParentAsInstruction() +diff --git a/transform/testdata/stringtobytes.ll b/transform/testdata/stringtobytes.ll +index fa43f3d0..06373a51 100644 +--- a/transform/testdata/stringtobytes.ll ++++ b/transform/testdata/stringtobytes.ll +@@ -30,3 +30,19 @@ entry: + call fastcc void @writeToSlice(ptr %1, i64 %2, i64 %3) + ret void + } ++ ++; Test that pointer values are never propagated if there is even a single write ++; to the pointer value (but len/cap values still can be). ++define void @testReadSome() { ++entry: ++ %s = call fastcc { ptr, i64, i64 } @runtime.stringToBytes(ptr @str, i64 6) ++ %s.ptr = extractvalue { ptr, i64, i64 } %s, 0 ++ %s.len = extractvalue { ptr, i64, i64 } %s, 1 ++ %s.cap = extractvalue { ptr, i64, i64 } %s, 2 ++ call fastcc void @writeToSlice(ptr %s.ptr, i64 %s.len, i64 %s.cap) ++ %s.ptr2 = extractvalue { ptr, i64, i64 } %s, 0 ++ %s.len2 = extractvalue { ptr, i64, i64 } %s, 1 ++ %s.cap2 = extractvalue { ptr, i64, i64 } %s, 2 ++ call fastcc void @printSlice(ptr %s.ptr2, i64 %s.len2, i64 %s.cap2) ++ ret void ++} +diff --git a/transform/testdata/stringtobytes.out.ll b/transform/testdata/stringtobytes.out.ll +index 30aa520a..b33a1755 100644 +--- a/transform/testdata/stringtobytes.out.ll ++++ b/transform/testdata/stringtobytes.out.ll +@@ -22,3 +22,13 @@ entry: + call fastcc void @writeToSlice(ptr %1, i64 6, i64 6) + ret void + } ++ ++define void @testReadSome() { ++entry: ++ %s = call fastcc { ptr, i64, i64 } @runtime.stringToBytes(ptr @str, i64 6) ++ %s.ptr = extractvalue { ptr, i64, i64 } %s, 0 ++ call fastcc void @writeToSlice(ptr %s.ptr, i64 6, i64 6) ++ %s.ptr2 = extractvalue { ptr, i64, i64 } %s, 0 ++ call fastcc void @printSlice(ptr %s.ptr2, i64 6, i64 6) ++ ret void ++} +-- +2.41.0 + diff --git a/0005-all-use-the-new-LLVM-pass-manager.patch b/0005-all-use-the-new-LLVM-pass-manager.patch new file mode 100644 index 0000000..84eadf2 --- /dev/null +++ b/0005-all-use-the-new-LLVM-pass-manager.patch @@ -0,0 +1,424 @@ +From 3dcb55f940011bb9bcb75d98182290da60642e4d Mon Sep 17 00:00:00 2001 +From: Ayke van Laethem +Date: Tue, 19 Sep 2023 22:37:44 +0200 +Subject: [PATCH 5/7] 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. + +Signed-off-by: Elliott Sales de Andrade +--- + builder/build.go | 19 +++-- + builder/sizes_test.go | 6 +- + compileopts/config.go | 12 ++-- + compiler/compiler_test.go | 12 ++-- + interp/interp_test.go | 9 +-- + transform/allocs_test.go | 11 +-- + transform/interface-lowering_test.go | 10 +-- + transform/maps_test.go | 11 +-- + transform/optimizer.go | 102 +++++++++------------------ + transform/transform.go | 2 +- + 10 files changed, 76 insertions(+), 118 deletions(-) + +diff --git a/builder/build.go b/builder/build.go +index dc360b92..d920a598 100644 +--- a/builder/build.go ++++ b/builder/build.go +@@ -83,8 +83,7 @@ type packageAction struct { + 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 + Imports map[string]string // map from imported package to action ID hash +- OptLevel int // LLVM optimization level (0-3) +- SizeLevel int // LLVM optimization for size level (0-2) ++ OptLevel string // LLVM optimization level (O0, O1, O2, Os, Oz) + 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) + } + +- optLevel, sizeLevel, _ := config.OptLevels() ++ optLevel, speedLevel, sizeLevel := config.OptLevel() + compilerConfig := &compiler.Config{ + Triple: config.Triple(), + CPU: config.CPU(), +@@ -321,7 +320,6 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe + EmbeddedFiles: make(map[string]string, len(allFiles)), + Imports: make(map[string]string, len(pkg.Pkg.Imports())), + OptLevel: optLevel, +- SizeLevel: sizeLevel, + UndefinedGlobals: undefinedGlobals, + } + for filePath, hash := range pkg.FileHashes { +@@ -743,17 +741,17 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe + if config.GOOS() == "windows" { + // Options for the MinGW wrapper for the lld COFF linker. + ldflags = append(ldflags, +- "-Xlink=/opt:lldlto="+strconv.Itoa(optLevel), ++ "-Xlink=/opt:lldlto="+strconv.Itoa(speedLevel), + "--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto")) + } else if config.GOOS() == "darwin" { + // Options for the ld64-compatible lld linker. + ldflags = append(ldflags, +- "--lto-O"+strconv.Itoa(optLevel), ++ "--lto-O"+strconv.Itoa(speedLevel), + "-cache_path_lto", filepath.Join(cacheDir, "thinlto")) + } else { + // Options for the ELF linker. + ldflags = append(ldflags, +- "--lto-O"+strconv.Itoa(optLevel), ++ "--lto-O"+strconv.Itoa(speedLevel), + "--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"), + ) + } +@@ -1066,10 +1064,9 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config) error { + return err + } + +- // Optimization levels here are roughly the same as Clang, but probably not +- // exactly. +- optLevel, sizeLevel, inlinerThreshold := config.OptLevels() +- errs := transform.Optimize(mod, config, optLevel, sizeLevel, inlinerThreshold) ++ // Run most of the whole-program optimizations (including the whole ++ // O0/O1/O2/Os/Oz optimization pipeline). ++ errs := transform.Optimize(mod, config) + if len(errs) > 0 { + return newMultiError(errs) + } +diff --git a/builder/sizes_test.go b/builder/sizes_test.go +index 7aaab78a..dc45898e 100644 +--- a/builder/sizes_test.go ++++ b/builder/sizes_test.go +@@ -41,9 +41,9 @@ func TestBinarySize(t *testing.T) { + // This is a small number of very diverse targets that we want to test. + tests := []sizeTest{ + // microcontrollers +- {"hifive1b", "examples/echo", 4568, 280, 0, 2252}, +- {"microbit", "examples/serial", 2728, 388, 8, 2256}, +- {"wioterminal", "examples/pininterrupt", 5996, 1484, 116, 6816}, ++ {"hifive1b", "examples/echo", 4484, 280, 0, 2252}, ++ {"microbit", "examples/serial", 2724, 388, 8, 2256}, ++ {"wioterminal", "examples/pininterrupt", 6000, 1484, 116, 6816}, + + // TODO: also check wasm. Right now this is difficult, because + // wasm binaries are run through wasm-opt and therefore the +diff --git a/compileopts/config.go b/compileopts/config.go +index 39fc4f2a..5ad45c60 100644 +--- a/compileopts/config.go ++++ b/compileopts/config.go +@@ -145,18 +145,18 @@ func (c *Config) Serial() string { + + // OptLevels returns the optimization level (0-2), size level (0-2), and inliner + // 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 { + case "none", "0": +- return 0, 0, 0 // -O0 ++ return "O0", 0, 0 + case "1": +- return 1, 0, 0 // -O1 ++ return "O1", 1, 0 + case "2": +- return 2, 0, 225 // -O2 ++ return "O2", 2, 0 + case "s": +- return 2, 1, 225 // -Os ++ return "Os", 2, 1 + case "z": +- return 2, 2, 5 // -Oz, default ++ return "Oz", 2, 2 // default + default: + // This is not shown to the user: valid choices are already checked as + // part of Options.Verify(). It is here as a sanity check. +diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go +index 92ce31b0..147e622a 100644 +--- a/compiler/compiler_test.go ++++ b/compiler/compiler_test.go +@@ -91,14 +91,12 @@ func TestCompiler(t *testing.T) { + } + + // Optimize IR a little. +- funcPasses := llvm.NewFunctionPassManagerForModule(mod) +- defer funcPasses.Dispose() +- funcPasses.AddInstructionCombiningPass() +- funcPasses.InitializeFunc() +- for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { +- funcPasses.RunFunc(fn) ++ passOptions := llvm.NewPassBuilderOptions() ++ defer passOptions.Dispose() ++ err = mod.RunPasses("instcombine", llvm.TargetMachine{}, passOptions) ++ if err != nil { ++ t.Error(err) + } +- funcPasses.FinalizeFunc() + + outFilePrefix := tc.file[:len(tc.file)-3] + if tc.target != "" { +diff --git a/interp/interp_test.go b/interp/interp_test.go +index fc567af2..cac56508 100644 +--- a/interp/interp_test.go ++++ b/interp/interp_test.go +@@ -77,12 +77,9 @@ func runTest(t *testing.T, pathPrefix string) { + } + + // Run some cleanup passes to get easy-to-read outputs. +- pm := llvm.NewPassManager() +- defer pm.Dispose() +- pm.AddGlobalOptimizerPass() +- pm.AddDeadStoreEliminationPass() +- pm.AddAggressiveDCEPass() +- pm.Run(mod) ++ to := llvm.NewPassBuilderOptions() ++ defer to.Dispose() ++ mod.RunPasses("globalopt,dse,adce", llvm.TargetMachine{}, to) + + // Read the expected output IR. + out, err := os.ReadFile(pathPrefix + ".out.ll") +diff --git a/transform/allocs_test.go b/transform/allocs_test.go +index 27bb9706..59a5b14e 100644 +--- a/transform/allocs_test.go ++++ b/transform/allocs_test.go +@@ -38,11 +38,12 @@ func TestAllocs2(t *testing.T) { + mod := compileGoFileForTesting(t, "./testdata/allocs2.go") + + // Run functionattrs pass, which is necessary for escape analysis. +- pm := llvm.NewPassManager() +- defer pm.Dispose() +- pm.AddInstructionCombiningPass() +- pm.AddFunctionAttrsPass() +- pm.Run(mod) ++ po := llvm.NewPassBuilderOptions() ++ defer po.Dispose() ++ err := mod.RunPasses("function(instcombine),function-attrs", llvm.TargetMachine{}, po) ++ if err != nil { ++ t.Error("failed to run passes:", err) ++ } + + // Run heap to stack transform. + var testOutputs []allocsTestOutput +diff --git a/transform/interface-lowering_test.go b/transform/interface-lowering_test.go +index 7bcce605..65f14dd9 100644 +--- a/transform/interface-lowering_test.go ++++ b/transform/interface-lowering_test.go +@@ -15,9 +15,11 @@ func TestInterfaceLowering(t *testing.T) { + t.Error(err) + } + +- pm := llvm.NewPassManager() +- defer pm.Dispose() +- pm.AddGlobalDCEPass() +- pm.Run(mod) ++ po := llvm.NewPassBuilderOptions() ++ defer po.Dispose() ++ err = mod.RunPasses("globaldce", llvm.TargetMachine{}, po) ++ if err != nil { ++ t.Error("failed to run passes:", err) ++ } + }) + } +diff --git a/transform/maps_test.go b/transform/maps_test.go +index e8b11133..329de698 100644 +--- a/transform/maps_test.go ++++ b/transform/maps_test.go +@@ -15,10 +15,11 @@ func TestOptimizeMaps(t *testing.T) { + + // Run an optimization pass, to clean up the result. + // This shows that all code related to the map is really eliminated. +- pm := llvm.NewPassManager() +- defer pm.Dispose() +- pm.AddDeadStoreEliminationPass() +- pm.AddAggressiveDCEPass() +- pm.Run(mod) ++ po := llvm.NewPassBuilderOptions() ++ defer po.Dispose() ++ err := mod.RunPasses("dse,adce", llvm.TargetMachine{}, po) ++ if err != nil { ++ t.Error("failed to run passes:", err) ++ } + }) + } +diff --git a/transform/optimizer.go b/transform/optimizer.go +index 20258ef4..42acc2dd 100644 +--- a/transform/optimizer.go ++++ b/transform/optimizer.go +@@ -14,54 +14,22 @@ import ( + // OptimizePackage runs optimization passes over the LLVM module for the given + // Go package. + func OptimizePackage(mod llvm.Module, config *compileopts.Config) { +- optLevel, sizeLevel, _ := config.OptLevels() +- +- // 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() ++ _, speedLevel, _ := config.OptLevel() + + // Run TinyGo-specific optimization passes. +- if optLevel > 0 { ++ if speedLevel > 0 { + OptimizeMaps(mod) + } + } + + // Optimize runs a number of optimization and transformation passes over the + // 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 +-// control the limits of the inliner (higher numbers mean more inlining, set it +-// to 0 to disable entirely). ++// passes. + // + // Please note that some optimizations are not optional, thus Optimize must +-// alwasy be run before emitting machine code. Set all controls (optLevel, +-// sizeLevel, inlinerThreshold) to 0 to reduce the number of optimizations to a +-// minimum. +-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) +- } ++// alwasy be run before emitting machine code. ++func Optimize(mod llvm.Module, config *compileopts.Config) []error { ++ optLevel, speedLevel, _ := config.OptLevel() + + // Make sure these functions are kept in tact during TinyGo transformation passes. + 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. +- goPasses := llvm.NewPassManager() +- defer goPasses.Dispose() +- goPasses.AddGlobalDCEPass() +- goPasses.AddGlobalOptimizerPass() +- goPasses.AddIPSCCPPass() +- goPasses.AddInstructionCombiningPass() // necessary for OptimizeReflectImplements +- goPasses.AddAggressiveDCEPass() +- goPasses.AddFunctionAttrsPass() +- goPasses.Run(mod) ++ po := llvm.NewPassBuilderOptions() ++ defer po.Dispose() ++ 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 optimization passes. + OptimizeStringToBytes(mod) + OptimizeReflectImplements(mod) + OptimizeAllocs(mod, nil, nil) +- err := LowerInterfaces(mod, config) ++ err = LowerInterfaces(mod, config) + if err != nil { + 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 + // interprocedural optimizations. To get them to work, function + // 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. + 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. +- goPasses := llvm.NewPassManager() +- defer goPasses.Dispose() +- goPasses.AddGlobalDCEPass() +- goPasses.Run(mod) ++ po := llvm.NewPassBuilderOptions() ++ defer po.Dispose() ++ err = mod.RunPasses("globaldce", llvm.TargetMachine{}, po) ++ if err != nil { ++ return []error{fmt.Errorf("could not build pass pipeline: %w", err)} ++ } + } + + if config.Scheduler() == "none" { +@@ -169,23 +139,15 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i + fn.SetLinkage(llvm.InternalLinkage) + } + +- // Run function passes again, because without it, llvm.coro.size.i32() +- // doesn't get lowered. +- funcPasses := llvm.NewFunctionPassManagerForModule(mod) +- defer funcPasses.Dispose() +- builder.PopulateFunc(funcPasses) +- funcPasses.InitializeFunc() +- for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { +- funcPasses.RunFunc(fn) ++ // Run the default pass pipeline. ++ // TODO: set the PrepareForThinLTO flag somehow. ++ po := llvm.NewPassBuilderOptions() ++ defer po.Dispose() ++ passes := fmt.Sprintf("default<%s>", optLevel) ++ err := mod.RunPasses(passes, llvm.TargetMachine{}, po) ++ if err != nil { ++ 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) + if hasGCPass { +diff --git a/transform/transform.go b/transform/transform.go +index ab08317e..429cbd5f 100644 +--- a/transform/transform.go ++++ b/transform/transform.go +@@ -22,7 +22,7 @@ import ( + // the -opt= compiler flag. + func AddStandardAttributes(fn llvm.Value, config *compileopts.Config) { + ctx := fn.Type().Context() +- _, sizeLevel, _ := config.OptLevels() ++ _, _, sizeLevel := config.OptLevel() + if sizeLevel >= 1 { + fn.AddFunctionAttr(ctx.CreateEnumAttribute(llvm.AttributeKindID("optsize"), 0)) + } +-- +2.41.0 + diff --git a/0006-avr-don-t-compile-large-parts-of-picolibc-math-stdio.patch b/0006-avr-don-t-compile-large-parts-of-picolibc-math-stdio.patch new file mode 100644 index 0000000..36bebd7 --- /dev/null +++ b/0006-avr-don-t-compile-large-parts-of-picolibc-math-stdio.patch @@ -0,0 +1,222 @@ +From 0321eecc6d29d5b571930ed97f392623fe80a2e5 Mon Sep 17 00:00:00 2001 +From: Ayke van Laethem +Date: Sat, 23 Sep 2023 14:57:25 +0200 +Subject: [PATCH 6/7] avr: don't compile large parts of picolibc (math, stdio) + +These parts aren't critical and lead to crashes on small chips without +long jumps (like the attiny85) with LLVM 17. (Older LLVM versions would +emit long jumps regardless, even if the chip didn't support those). + +For more information, see: https://github.com/llvm/llvm-project/issues/67042 + +Signed-off-by: Elliott Sales de Andrade +--- + builder/picolibc.go | 172 ++++++++++++++++++++++++-------------------- + 1 file changed, 93 insertions(+), 79 deletions(-) + +diff --git a/builder/picolibc.go b/builder/picolibc.go +index 1b7c748b..91ad27be 100644 +--- a/builder/picolibc.go ++++ b/builder/picolibc.go +@@ -3,6 +3,7 @@ package builder + import ( + "os" + "path/filepath" ++ "strings" + + "github.com/tinygo-org/tinygo/goenv" + ) +@@ -41,91 +42,23 @@ var Picolibc = Library{ + }, + sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib") }, + librarySources: func(target string) ([]string, error) { +- return picolibcSources, nil ++ sources := append([]string(nil), picolibcSources...) ++ if !strings.HasPrefix(target, "avr") { ++ // Small chips without long jumps can't compile many files (printf, ++ // pow, etc). Therefore exclude those source files for those chips. ++ // Unfortunately it's difficult to exclude only some chips, so this ++ // excludes those files on all AVR chips for now. ++ // More information: ++ // https://github.com/llvm/llvm-project/issues/67042 ++ sources = append(sources, picolibcSourcesLarge...) ++ } ++ return sources, nil + }, + } + + var picolibcSources = []string{ + "../../picolibc-stdio.c", + +- // srcs_tinystdio +- "libc/tinystdio/asprintf.c", +- "libc/tinystdio/bufio.c", +- "libc/tinystdio/clearerr.c", +- "libc/tinystdio/ecvt_r.c", +- "libc/tinystdio/ecvt.c", +- "libc/tinystdio/ecvtf_r.c", +- "libc/tinystdio/ecvtf.c", +- "libc/tinystdio/fcvt.c", +- "libc/tinystdio/fcvt_r.c", +- "libc/tinystdio/fcvtf.c", +- "libc/tinystdio/fcvtf_r.c", +- "libc/tinystdio/gcvt.c", +- "libc/tinystdio/gcvtf.c", +- "libc/tinystdio/fclose.c", +- "libc/tinystdio/fdevopen.c", +- "libc/tinystdio/feof.c", +- "libc/tinystdio/ferror.c", +- "libc/tinystdio/fflush.c", +- "libc/tinystdio/fgetc.c", +- "libc/tinystdio/fgets.c", +- "libc/tinystdio/fileno.c", +- "libc/tinystdio/filestrget.c", +- "libc/tinystdio/filestrput.c", +- "libc/tinystdio/filestrputalloc.c", +- "libc/tinystdio/fmemopen.c", +- "libc/tinystdio/fprintf.c", +- "libc/tinystdio/fputc.c", +- "libc/tinystdio/fputs.c", +- "libc/tinystdio/fread.c", +- //"libc/tinystdio/freopen.c", // crashes with AVR, see: https://github.com/picolibc/picolibc/pull/369 +- "libc/tinystdio/fscanf.c", +- "libc/tinystdio/fseek.c", +- "libc/tinystdio/fseeko.c", +- "libc/tinystdio/ftell.c", +- "libc/tinystdio/ftello.c", +- "libc/tinystdio/fwrite.c", +- "libc/tinystdio/getchar.c", +- "libc/tinystdio/gets.c", +- "libc/tinystdio/matchcaseprefix.c", +- "libc/tinystdio/mktemp.c", +- "libc/tinystdio/perror.c", +- "libc/tinystdio/printf.c", +- "libc/tinystdio/putchar.c", +- "libc/tinystdio/puts.c", +- "libc/tinystdio/rewind.c", +- "libc/tinystdio/scanf.c", +- "libc/tinystdio/setbuf.c", +- "libc/tinystdio/setbuffer.c", +- "libc/tinystdio/setlinebuf.c", +- "libc/tinystdio/setvbuf.c", +- "libc/tinystdio/snprintf.c", +- "libc/tinystdio/sprintf.c", +- "libc/tinystdio/snprintfd.c", +- "libc/tinystdio/snprintff.c", +- "libc/tinystdio/sprintff.c", +- "libc/tinystdio/sprintfd.c", +- "libc/tinystdio/sscanf.c", +- "libc/tinystdio/strfromf.c", +- "libc/tinystdio/strfromd.c", +- "libc/tinystdio/strtof.c", +- "libc/tinystdio/strtof_l.c", +- "libc/tinystdio/strtod.c", +- "libc/tinystdio/strtod_l.c", +- "libc/tinystdio/ungetc.c", +- "libc/tinystdio/vasprintf.c", +- "libc/tinystdio/vfiprintf.c", +- "libc/tinystdio/vfprintf.c", +- "libc/tinystdio/vfprintff.c", +- "libc/tinystdio/vfscanf.c", +- "libc/tinystdio/vfiscanf.c", +- "libc/tinystdio/vfscanff.c", +- "libc/tinystdio/vprintf.c", +- "libc/tinystdio/vscanf.c", +- "libc/tinystdio/vsscanf.c", +- "libc/tinystdio/vsnprintf.c", +- "libc/tinystdio/vsprintf.c", +- + "libc/string/bcmp.c", + "libc/string/bcopy.c", + "libc/string/bzero.c", +@@ -229,6 +162,87 @@ var picolibcSources = []string{ + "libc/string/wmempcpy.c", + "libc/string/wmemset.c", + "libc/string/xpg_strerror_r.c", ++} ++ ++// Parts of picolibc that are too large for small AVRs. ++var picolibcSourcesLarge = []string{ ++ // srcs_tinystdio ++ "libc/tinystdio/asprintf.c", ++ "libc/tinystdio/bufio.c", ++ "libc/tinystdio/clearerr.c", ++ "libc/tinystdio/ecvt_r.c", ++ "libc/tinystdio/ecvt.c", ++ "libc/tinystdio/ecvtf_r.c", ++ "libc/tinystdio/ecvtf.c", ++ "libc/tinystdio/fcvt.c", ++ "libc/tinystdio/fcvt_r.c", ++ "libc/tinystdio/fcvtf.c", ++ "libc/tinystdio/fcvtf_r.c", ++ "libc/tinystdio/gcvt.c", ++ "libc/tinystdio/gcvtf.c", ++ "libc/tinystdio/fclose.c", ++ "libc/tinystdio/fdevopen.c", ++ "libc/tinystdio/feof.c", ++ "libc/tinystdio/ferror.c", ++ "libc/tinystdio/fflush.c", ++ "libc/tinystdio/fgetc.c", ++ "libc/tinystdio/fgets.c", ++ "libc/tinystdio/fileno.c", ++ "libc/tinystdio/filestrget.c", ++ "libc/tinystdio/filestrput.c", ++ "libc/tinystdio/filestrputalloc.c", ++ "libc/tinystdio/fmemopen.c", ++ "libc/tinystdio/fprintf.c", ++ "libc/tinystdio/fputc.c", ++ "libc/tinystdio/fputs.c", ++ "libc/tinystdio/fread.c", ++ //"libc/tinystdio/freopen.c", // crashes with AVR, see: https://github.com/picolibc/picolibc/pull/369 ++ "libc/tinystdio/fscanf.c", ++ "libc/tinystdio/fseek.c", ++ "libc/tinystdio/fseeko.c", ++ "libc/tinystdio/ftell.c", ++ "libc/tinystdio/ftello.c", ++ "libc/tinystdio/fwrite.c", ++ "libc/tinystdio/getchar.c", ++ "libc/tinystdio/gets.c", ++ "libc/tinystdio/matchcaseprefix.c", ++ "libc/tinystdio/mktemp.c", ++ "libc/tinystdio/perror.c", ++ "libc/tinystdio/printf.c", ++ "libc/tinystdio/putchar.c", ++ "libc/tinystdio/puts.c", ++ "libc/tinystdio/rewind.c", ++ "libc/tinystdio/scanf.c", ++ "libc/tinystdio/setbuf.c", ++ "libc/tinystdio/setbuffer.c", ++ "libc/tinystdio/setlinebuf.c", ++ "libc/tinystdio/setvbuf.c", ++ "libc/tinystdio/snprintf.c", ++ "libc/tinystdio/sprintf.c", ++ "libc/tinystdio/snprintfd.c", ++ "libc/tinystdio/snprintff.c", ++ "libc/tinystdio/sprintff.c", ++ "libc/tinystdio/sprintfd.c", ++ "libc/tinystdio/sscanf.c", ++ "libc/tinystdio/strfromf.c", ++ "libc/tinystdio/strfromd.c", ++ "libc/tinystdio/strtof.c", ++ "libc/tinystdio/strtof_l.c", ++ "libc/tinystdio/strtod.c", ++ "libc/tinystdio/strtod_l.c", ++ "libc/tinystdio/ungetc.c", ++ "libc/tinystdio/vasprintf.c", ++ "libc/tinystdio/vfiprintf.c", ++ "libc/tinystdio/vfprintf.c", ++ "libc/tinystdio/vfprintff.c", ++ "libc/tinystdio/vfscanf.c", ++ "libc/tinystdio/vfiscanf.c", ++ "libc/tinystdio/vfscanff.c", ++ "libc/tinystdio/vprintf.c", ++ "libc/tinystdio/vscanf.c", ++ "libc/tinystdio/vsscanf.c", ++ "libc/tinystdio/vsnprintf.c", ++ "libc/tinystdio/vsprintf.c", + + "libm/common/sf_finite.c", + "libm/common/sf_copysign.c", +-- +2.41.0 + diff --git a/0007-all-add-initial-LLVM-17-support.patch b/0007-all-add-initial-LLVM-17-support.patch new file mode 100644 index 0000000..1650c1c --- /dev/null +++ b/0007-all-add-initial-LLVM-17-support.patch @@ -0,0 +1,157 @@ +From 55fd04af5a6a815224b436ee491ed05b0ef4ee7c Mon Sep 17 00:00:00 2001 +From: Ayke van Laethem +Date: Sat, 23 Sep 2023 15:03:24 +0200 +Subject: [PATCH 7/7] all: add initial LLVM 17 support + +This allows us to test LLVM 17 already, before it's available in +Homebrew etc. + +Full support for LLVM 17 will have to wait until Espressif rebases their +Xtensa fork of LLVM. + +Signed-off-by: Elliott Sales de Andrade +--- + .circleci/config.yml | 13 +++++++++++-- + .github/workflows/build-macos.yml | 16 +++++++++++++--- + cgo/libclang_config_llvm16.go | 2 +- + cgo/libclang_config_llvm17.go | 15 +++++++++++++++ + go.mod | 2 +- + go.sum | 4 ++-- + 6 files changed, 43 insertions(+), 9 deletions(-) + create mode 100644 cgo/libclang_config_llvm17.go + +diff --git a/.circleci/config.yml b/.circleci/config.yml +index 1dff3ba7..86da321d 100644 +--- a/.circleci/config.yml ++++ b/.circleci/config.yml +@@ -55,7 +55,7 @@ commands: + - run: + name: "Install apt dependencies" + command: | +- echo 'deb https://apt.llvm.org/buster/ llvm-toolchain-buster-<> main' > /etc/apt/sources.list.d/llvm.list ++ echo 'deb https://apt.llvm.org/bullseye/ llvm-toolchain-bullseye-<> main' > /etc/apt/sources.list.d/llvm.list + wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - + apt-get update + apt-get install --no-install-recommends -y \ +@@ -100,11 +100,18 @@ commands: + jobs: + test-llvm14-go118: + docker: +- - image: golang:1.18-buster ++ - image: golang:1.18-bullseye + steps: + - test-linux: + llvm: "14" + resource_class: large ++ test-llvm17-go121: ++ docker: ++ - image: golang:1.21-bullseye ++ steps: ++ - test-linux: ++ llvm: "17" ++ resource_class: large + + workflows: + test-all: +@@ -112,3 +119,5 @@ workflows: + # This tests our lowest supported versions of Go and LLVM, to make sure at + # least the smoke tests still pass. + - test-llvm14-go118 ++ # This tests the upcoming LLVM 17 support. ++ - test-llvm17-go121 +diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml +index 1284a4ed..be8bdb03 100644 +--- a/.github/workflows/build-macos.yml ++++ b/.github/workflows/build-macos.yml +@@ -116,11 +116,15 @@ jobs: + test-macos-homebrew: + name: homebrew-install + runs-on: macos-latest ++ strategy: ++ matrix: ++ version: [16, 17] + steps: ++ - name: Update Homebrew ++ run: brew update + - name: Install LLVM +- shell: bash + run: | +- HOMEBREW_NO_AUTO_UPDATE=1 brew install llvm@16 ++ HOMEBREW_NO_AUTO_UPDATE=1 brew install llvm@${{ matrix.version }} + - name: Checkout + uses: actions/checkout@v3 + - name: Install Go +@@ -128,7 +132,13 @@ jobs: + with: + go-version: '1.21' + cache: true +- - name: Build TinyGo ++ - name: Build TinyGo (LLVM ${{ matrix.version }}) ++ run: go install -tags=llvm${{ matrix.version }} ++ - name: Check binary ++ run: tinygo version ++ - name: Build TinyGo (default LLVM) ++ if: matrix.version == 16 + run: go install + - name: Check binary ++ if: matrix.version == 16 + run: tinygo version +diff --git a/cgo/libclang_config_llvm16.go b/cgo/libclang_config_llvm16.go +index 79aacd2f..28091701 100644 +--- a/cgo/libclang_config_llvm16.go ++++ b/cgo/libclang_config_llvm16.go +@@ -1,4 +1,4 @@ +-//go:build !byollvm && !llvm14 && !llvm15 ++//go:build !byollvm && !llvm14 && !llvm15 && !llvm17 + + package cgo + +diff --git a/cgo/libclang_config_llvm17.go b/cgo/libclang_config_llvm17.go +new file mode 100644 +index 00000000..fd6d1480 +--- /dev/null ++++ b/cgo/libclang_config_llvm17.go +@@ -0,0 +1,15 @@ ++//go:build !byollvm && llvm17 ++ ++package cgo ++ ++/* ++#cgo linux CFLAGS: -I/usr/include/llvm-17 -I/usr/include/llvm-c-17 -I/usr/lib/llvm-17/include ++#cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@17/include ++#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@17/include ++#cgo freebsd CFLAGS: -I/usr/local/llvm17/include ++#cgo linux LDFLAGS: -L/usr/lib/llvm-17/lib -lclang ++#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@17/lib -lclang -lffi ++#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@17/lib -lclang -lffi ++#cgo freebsd LDFLAGS: -L/usr/local/llvm17/lib -lclang ++*/ ++import "C" +diff --git a/go.mod b/go.mod +index 9af3402b..36d601f8 100644 +--- a/go.mod ++++ b/go.mod +@@ -18,7 +18,7 @@ require ( + golang.org/x/sys v0.11.0 + golang.org/x/tools v0.12.0 + gopkg.in/yaml.v2 v2.4.0 +- tinygo.org/x/go-llvm v0.0.0-20230918183930-9edb6403d0bc ++ tinygo.org/x/go-llvm v0.0.0-20230923132128-bba3c7009bfd + ) + + require ( +diff --git a/go.sum b/go.sum +index 587efdf2..1b5ad074 100644 +--- a/go.sum ++++ b/go.sum +@@ -65,5 +65,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 + gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= + gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +-tinygo.org/x/go-llvm v0.0.0-20230918183930-9edb6403d0bc h1:IVX1dqCX3c88P7iEMBtz1xCAM4UIqCMgbqHdSefBaWE= +-tinygo.org/x/go-llvm v0.0.0-20230918183930-9edb6403d0bc/go.mod h1:GFbusT2VTA4I+l4j80b17KFK+6whv69Wtny5U+T8RR0= ++tinygo.org/x/go-llvm v0.0.0-20230923132128-bba3c7009bfd h1:iSVvs8r3ooxYTsmcf7FKCHq83eOSUkWKtmQhnDadDQU= ++tinygo.org/x/go-llvm v0.0.0-20230923132128-bba3c7009bfd/go.mod h1:GFbusT2VTA4I+l4j80b17KFK+6whv69Wtny5U+T8RR0= +-- +2.41.0 + diff --git a/tinygo.spec b/tinygo.spec index d13baeb..f9ed1bf 100644 --- a/tinygo.spec +++ b/tinygo.spec @@ -77,6 +77,15 @@ Patch0002: 0002-Skip-some-cross-Linux-tests-where-qemu-is-broken.patch # Add Fedora specific dnf instructions Patch0003: 0003-Suggest-optional-packages-to-install-if-missing.patch +# Support LLVM 17. +# https://github.com/tinygo-org/tinygo/pull/3923 +Patch0004: 0004-transform-fix-bug-in-StringToBytes-optimization-pass.patch +# https://github.com/tinygo-org/tinygo/pull/3918 +Patch0005: 0005-all-use-the-new-LLVM-pass-manager.patch +# https://github.com/tinygo-org/tinygo/pull/3913 +Patch0006: 0006-avr-don-t-compile-large-parts-of-picolibc-math-stdio.patch +Patch0007: 0007-all-add-initial-LLVM-17-support.patch + # Not supported upstream yet. ExcludeArch: ppc64le s390x # https://fedoraproject.org/wiki/Changes/EncourageI686LeafRemoval @@ -97,7 +106,7 @@ BuildRequires: golang(github.com/mattn/go-tty) >= 0.0.4 BuildRequires: golang(github.com/sigurn/crc16) BuildRequires: golang(go.bug.st/serial) >= 1.6.0 BuildRequires: golang(golang.org/x/tools/go/ast/astutil) -BuildRequires: golang(golang.org/x/tools/go/ssa) >= 0.12 +BuildRequires: golang(golang.org/x/tools/go/ssa) >= 0.11 BuildRequires: golang(gopkg.in/yaml.v2) >= 2.4.0 BuildRequires: golang(tinygo.org/x/go-llvm) BuildRequires: golang-tests