From 9450ee8181d2b316a1e836b8beea3b21b753b948 Mon Sep 17 00:00:00 2001 From: Hector Chu Date: Tue, 14 Apr 2026 19:52:21 +0100 Subject: [PATCH 01/30] initial --- builder/build.go | 42 ++++++++++++++++++++++++++++++++++- builder/commands.go | 2 ++ builder/tools.go | 2 +- compileopts/config.go | 3 +++ compileopts/options.go | 2 +- compileopts/target.go | 9 ++++++-- src/runtime/scheduler_none.go | 9 ++++++++ 7 files changed, 64 insertions(+), 5 deletions(-) diff --git a/builder/build.go b/builder/build.go index 714a331a39..3507965b82 100644 --- a/builder/build.go +++ b/builder/build.go @@ -785,7 +785,9 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe } // Add libc dependencies, if they exist. - linkerDependencies = append(linkerDependencies, libcDependencies...) + if config.BuildMode() != "c-archive" { + linkerDependencies = append(linkerDependencies, libcDependencies...) + } // Add embedded files. linkerDependencies = append(linkerDependencies, embedFileObjects...) @@ -828,6 +830,44 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe } ldflags = append(ldflags, dependency.result) } + + if config.BuildMode() == "c-archive" { + ctx := llvm.NewContext() + mod = ctx.NewModule("main") + for _, dependency := range job.dependencies { + if !strings.HasSuffix(dependency.description, ".S") { + depMod, err := mod.Context().ParseBitcodeFile(dependency.result) + if err != nil { + return err + } + err = llvm.LinkModules(mod, depMod) + if err != nil { + return err + } + } + } + if fn := mod.NamedFunction("main"); !fn.IsNil() { + fn.EraseFromParentAsFunction() + } + buf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile) + if err != nil { + return err + } + defer buf.Dispose() + err = os.WriteFile(result.Executable, buf.Bytes(), 0666) + if err != nil { + return err + } + result.Binary = result.Executable + ".a" + args := []string{"rcs", result.Binary, result.Executable} + for _, dependency := range job.dependencies { + if strings.HasSuffix(dependency.description, ".S") { + args = append(args, dependency.result) + } + } + return link("llvm-ar", args...) + } + ldflags = append(ldflags, "-mllvm", "-mcpu="+config.CPU()) ldflags = append(ldflags, "-mllvm", "-mattr="+config.Features()) // needed for MIPS softfloat if config.GOOS() == "windows" { diff --git a/builder/commands.go b/builder/commands.go index d804ee1476..137cee0519 100644 --- a/builder/commands.go +++ b/builder/commands.go @@ -22,6 +22,7 @@ func init() { commands["ld.lld"] = []string{"ld.lld-" + llvmMajor, "ld.lld"} commands["wasm-ld"] = []string{"wasm-ld-" + llvmMajor, "wasm-ld"} commands["lldb"] = []string{"lldb-" + llvmMajor, "lldb"} + commands["llvm-ar"] = []string{"llvm-ar-" + llvmMajor, "llvm-ar"} // Add the path to a Homebrew-installed LLVM for ease of use (no need to // manually set $PATH). if runtime.GOOS == "darwin" { @@ -39,6 +40,7 @@ func init() { commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld") commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld") commands["lldb"] = append(commands["lldb"], prefix+"lldb") + commands["llvm-ar"] = append(commands["llvm-ar"], prefix+"llvm-ar") } // Add the path for when LLVM was installed with the installer from // llvm.org, which by default doesn't add LLVM to the $PATH environment diff --git a/builder/tools.go b/builder/tools.go index 9d15e4ccaa..992460fe06 100644 --- a/builder/tools.go +++ b/builder/tools.go @@ -58,7 +58,7 @@ func runCCompiler(flags ...string) error { // link invokes a linker with the given name and flags. func link(linker string, flags ...string) error { // We only support LLD. - if linker != "ld.lld" && linker != "wasm-ld" { + if linker != "ld.lld" && linker != "wasm-ld" && linker != "llvm-ar" { return fmt.Errorf("unexpected: linker %s should be ld.lld or wasm-ld", linker) } diff --git a/compileopts/config.go b/compileopts/config.go index 1920d2b9cb..639f577f54 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -325,6 +325,9 @@ func (c *Config) DefaultBinaryExtension() string { // I think it's a good tradition, so let's keep it. return ".elf" } + if c.BuildMode() == "c-archive" { + return ".a" + } // Linux, MacOS, etc, don't use a file extension. Use it as a fallback. return "" } diff --git a/compileopts/options.go b/compileopts/options.go index e543dca459..469a4fb45b 100644 --- a/compileopts/options.go +++ b/compileopts/options.go @@ -8,7 +8,7 @@ import ( ) var ( - validBuildModeOptions = []string{"default", "c-shared", "wasi-legacy"} + validBuildModeOptions = []string{"default", "c-archive", "c-shared", "wasi-legacy"} validGCOptions = []string{"none", "leaking", "conservative", "custom", "precise", "boehm"} validSchedulerOptions = []string{"none", "tasks", "asyncify", "threads", "cores"} validSerialOptions = []string{"none", "uart", "usb", "rtt"} diff --git a/compileopts/target.go b/compileopts/target.go index 4e0269e6b0..43ec36004b 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -398,7 +398,9 @@ func defaultTarget(options *Options) (*TargetSpec, error) { llvmvendor := "unknown" switch options.GOOS { case "darwin": - spec.GC = "boehm" + if options.GC == "boehm" { + spec.GC = "boehm" + } platformVersion := "10.12.0" if options.GOARCH == "arm64" { platformVersion = "11.0.0" // first macosx platform with arm64 support @@ -418,10 +420,13 @@ func defaultTarget(options *Options) (*TargetSpec, error) { ) spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/futex/futex_darwin.c", - "src/internal/task/task_threads.c", "src/runtime/os_darwin.c", "src/runtime/runtime_unix.c", "src/runtime/signal.c") + if options.Scheduler == "threads" { + spec.ExtraFiles = append(spec.ExtraFiles, + "src/internal/task/task_threads.c") + } case "linux": spec.GC = "boehm" spec.Scheduler = "threads" diff --git a/src/runtime/scheduler_none.go b/src/runtime/scheduler_none.go index 7414172737..1091e543be 100644 --- a/src/runtime/scheduler_none.go +++ b/src/runtime/scheduler_none.go @@ -28,6 +28,15 @@ func run() { mainExited = true } +//export tinygo_init +func tinygo_init() { + allocateHeap() + stackTop = getCurrentStackPointer() + initRand() + initHeap() + initAll() +} + //go:linkname sleep time.Sleep func sleep(duration int64) { if duration <= 0 { From 02e150a55f31b84ba7ad720a21fe16cc483caa4b Mon Sep 17 00:00:00 2001 From: Hector Chu Date: Tue, 14 Apr 2026 23:12:36 +0100 Subject: [PATCH 02/30] fix esp32 --- builder/build.go | 2 +- src/runtime/baremetal.go | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/builder/build.go b/builder/build.go index 3507965b82..4266e5923f 100644 --- a/builder/build.go +++ b/builder/build.go @@ -835,7 +835,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe ctx := llvm.NewContext() mod = ctx.NewModule("main") for _, dependency := range job.dependencies { - if !strings.HasSuffix(dependency.description, ".S") { + if !strings.HasSuffix(dependency.description, ".S") && dependency.description != "" { depMod, err := mod.Context().ParseBitcodeFile(dependency.result) if err != nil { return err diff --git a/src/runtime/baremetal.go b/src/runtime/baremetal.go index 9915f191b2..c8b7a85b50 100644 --- a/src/runtime/baremetal.go +++ b/src/runtime/baremetal.go @@ -30,6 +30,8 @@ var ( stackTop = uintptr(unsafe.Pointer(&stackTopSymbol)) ) +func allocateHeap() {} + // growHeap tries to grow the heap size. It returns true if it succeeds, false // otherwise. func growHeap() bool { @@ -60,6 +62,11 @@ func runtime_putchar(c byte) { putchar(c) } +//export abort +func runtime_abort() { + abort() +} + //go:linkname syscall_Exit syscall.Exit func syscall_Exit(code int) { exit(code) From 0491538d9c5469b50a541ddeaf962ff6b3fbc234 Mon Sep 17 00:00:00 2001 From: Hector Chu Date: Wed, 15 Apr 2026 07:10:48 +0100 Subject: [PATCH 03/30] fix ar exec --- builder/build.go | 6 +++++- builder/tools.go | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/builder/build.go b/builder/build.go index 4266e5923f..89ab192ebe 100644 --- a/builder/build.go +++ b/builder/build.go @@ -865,7 +865,11 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe args = append(args, dependency.result) } } - return link("llvm-ar", args...) + name, err := LookupCommand("llvm-ar") + if err != nil { + return err + } + return exec.Command(name, args...).Run() } ldflags = append(ldflags, "-mllvm", "-mcpu="+config.CPU()) diff --git a/builder/tools.go b/builder/tools.go index 992460fe06..9d15e4ccaa 100644 --- a/builder/tools.go +++ b/builder/tools.go @@ -58,7 +58,7 @@ func runCCompiler(flags ...string) error { // link invokes a linker with the given name and flags. func link(linker string, flags ...string) error { // We only support LLD. - if linker != "ld.lld" && linker != "wasm-ld" && linker != "llvm-ar" { + if linker != "ld.lld" && linker != "wasm-ld" { return fmt.Errorf("unexpected: linker %s should be ld.lld or wasm-ld", linker) } From 98e3f5b0cd57f504a0d4c17068112576508d47d7 Mon Sep 17 00:00:00 2001 From: Hector Chu Date: Wed, 15 Apr 2026 12:37:53 +0100 Subject: [PATCH 04/30] use builtin ar --- builder/build.go | 9 +++++---- builder/commands.go | 2 -- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/builder/build.go b/builder/build.go index 89ab192ebe..4fab98c481 100644 --- a/builder/build.go +++ b/builder/build.go @@ -859,17 +859,18 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe return err } result.Binary = result.Executable + ".a" - args := []string{"rcs", result.Binary, result.Executable} + objs := []string{result.Executable} for _, dependency := range job.dependencies { if strings.HasSuffix(dependency.description, ".S") { - args = append(args, dependency.result) + objs = append(objs, dependency.result) } } - name, err := LookupCommand("llvm-ar") + f, err := os.Create(result.Binary) if err != nil { return err } - return exec.Command(name, args...).Run() + defer f.Close() + return makeArchive(f, objs) } ldflags = append(ldflags, "-mllvm", "-mcpu="+config.CPU()) diff --git a/builder/commands.go b/builder/commands.go index 137cee0519..d804ee1476 100644 --- a/builder/commands.go +++ b/builder/commands.go @@ -22,7 +22,6 @@ func init() { commands["ld.lld"] = []string{"ld.lld-" + llvmMajor, "ld.lld"} commands["wasm-ld"] = []string{"wasm-ld-" + llvmMajor, "wasm-ld"} commands["lldb"] = []string{"lldb-" + llvmMajor, "lldb"} - commands["llvm-ar"] = []string{"llvm-ar-" + llvmMajor, "llvm-ar"} // Add the path to a Homebrew-installed LLVM for ease of use (no need to // manually set $PATH). if runtime.GOOS == "darwin" { @@ -40,7 +39,6 @@ func init() { commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld") commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld") commands["lldb"] = append(commands["lldb"], prefix+"lldb") - commands["llvm-ar"] = append(commands["llvm-ar"], prefix+"llvm-ar") } // Add the path for when LLVM was installed with the installer from // llvm.org, which by default doesn't add LLVM to the $PATH environment From f9d09eee31d8c08b1e86deb89890f66ffa0f901a Mon Sep 17 00:00:00 2001 From: Hector Chu Date: Wed, 15 Apr 2026 18:50:38 +0100 Subject: [PATCH 05/30] xtensa: write each function to its own section --- builder/build.go | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/builder/build.go b/builder/build.go index 4fab98c481..fe1b0a0c6d 100644 --- a/builder/build.go +++ b/builder/build.go @@ -835,20 +835,28 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe ctx := llvm.NewContext() mod = ctx.NewModule("main") for _, dependency := range job.dependencies { - if !strings.HasSuffix(dependency.description, ".S") && dependency.description != "" { - depMod, err := mod.Context().ParseBitcodeFile(dependency.result) - if err != nil { - return err - } - err = llvm.LinkModules(mod, depMod) - if err != nil { - return err - } + if strings.HasSuffix(dependency.description, ".S") || dependency.description == "" { + continue + } + depMod, err := mod.Context().ParseBitcodeFile(dependency.result) + if err != nil { + return err + } + err = llvm.LinkModules(mod, depMod) + if err != nil { + return err } } if fn := mod.NamedFunction("main"); !fn.IsNil() { fn.EraseFromParentAsFunction() } + if config.Triple() == "xtensa" { + for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { + if !fn.IsDeclaration() { + fn.SetSection(".text." + fn.Name()) + } + } + } buf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile) if err != nil { return err From 21dce589067d0520ffa6283135979d27888f86ca Mon Sep 17 00:00:00 2001 From: Hector Chu Date: Wed, 15 Apr 2026 20:06:13 +0100 Subject: [PATCH 06/30] fix multiple definitions --- builder/build.go | 13 +++++++++++-- src/runtime/baremetal.go | 5 ----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/builder/build.go b/builder/build.go index fe1b0a0c6d..535d1ef09c 100644 --- a/builder/build.go +++ b/builder/build.go @@ -835,10 +835,11 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe ctx := llvm.NewContext() mod = ctx.NewModule("main") for _, dependency := range job.dependencies { - if strings.HasSuffix(dependency.description, ".S") || dependency.description == "" { + if strings.HasSuffix(dependency.description, ".S") || + dependency.description == "" { continue } - depMod, err := mod.Context().ParseBitcodeFile(dependency.result) + depMod, err := ctx.ParseBitcodeFile(dependency.result) if err != nil { return err } @@ -852,6 +853,14 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe } if config.Triple() == "xtensa" { for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { + switch fn.Name() { + case "malloc", "calloc", "free": + fn.SetLinkage(llvm.InternalLinkage) + } + if strings.HasPrefix(fn.Name(), "__atomic_") || + strings.HasPrefix(fn.Name(), "__sync_") { + fn.SetLinkage(llvm.InternalLinkage) + } if !fn.IsDeclaration() { fn.SetSection(".text." + fn.Name()) } diff --git a/src/runtime/baremetal.go b/src/runtime/baremetal.go index c8b7a85b50..c42787197b 100644 --- a/src/runtime/baremetal.go +++ b/src/runtime/baremetal.go @@ -62,11 +62,6 @@ func runtime_putchar(c byte) { putchar(c) } -//export abort -func runtime_abort() { - abort() -} - //go:linkname syscall_Exit syscall.Exit func syscall_Exit(code int) { exit(code) From cc16c7d8a31c37bda3d72b97b00f6d590ef3ee46 Mon Sep 17 00:00:00 2001 From: Hector Chu Date: Wed, 15 Apr 2026 21:37:07 +0100 Subject: [PATCH 07/30] link obj files together --- builder/build.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/builder/build.go b/builder/build.go index 535d1ef09c..f2c234f03c 100644 --- a/builder/build.go +++ b/builder/build.go @@ -861,8 +861,9 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe strings.HasPrefix(fn.Name(), "__sync_") { fn.SetLinkage(llvm.InternalLinkage) } - if !fn.IsDeclaration() { - fn.SetSection(".text." + fn.Name()) + fn.SetSection(".text." + fn.Name()) + if fn.Name() == "tinygo_scanstack" { + fn.SetSection(".text.tinygo_scanCurrentStack") } } } @@ -875,19 +876,24 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe if err != nil { return err } - result.Binary = result.Executable + ".a" objs := []string{result.Executable} for _, dependency := range job.dependencies { if strings.HasSuffix(dependency.description, ".S") { objs = append(objs, dependency.result) } } + objfile := result.Executable + ".o" + err = link(config.Target.Linker, append(objs, "-r", "-o", objfile)...) + if err != nil { + return err + } + result.Binary = result.Executable + ".a" f, err := os.Create(result.Binary) if err != nil { return err } defer f.Close() - return makeArchive(f, objs) + return makeArchive(f, []string{objfile}) } ldflags = append(ldflags, "-mllvm", "-mcpu="+config.CPU()) From f4fb3469b36d14b6b93052c3174d5fe1affd55b2 Mon Sep 17 00:00:00 2001 From: Hector Chu Date: Wed, 15 Apr 2026 21:56:32 +0100 Subject: [PATCH 08/30] only link for xtensa --- builder/build.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/builder/build.go b/builder/build.go index f2c234f03c..8c601a17d1 100644 --- a/builder/build.go +++ b/builder/build.go @@ -882,10 +882,13 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe objs = append(objs, dependency.result) } } - objfile := result.Executable + ".o" - err = link(config.Target.Linker, append(objs, "-r", "-o", objfile)...) - if err != nil { - return err + if config.Triple() == "xtensa" { + obj := result.Executable + ".o" + err = link(config.Target.Linker, append(objs, "-r", "-o", obj)...) + if err != nil { + return err + } + objs = []string{obj} } result.Binary = result.Executable + ".a" f, err := os.Create(result.Binary) @@ -893,7 +896,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe return err } defer f.Close() - return makeArchive(f, []string{objfile}) + return makeArchive(f, objs) } ldflags = append(ldflags, "-mllvm", "-mcpu="+config.CPU()) From ab5ef7e5898ac45da11a282bf5555cf287cd37cf Mon Sep 17 00:00:00 2001 From: Hector Chu Date: Wed, 15 Apr 2026 22:45:45 +0100 Subject: [PATCH 09/30] remove call_start_cpu0 --- builder/build.go | 8 ++++++++ builder/commands.go | 2 ++ 2 files changed, 10 insertions(+) diff --git a/builder/build.go b/builder/build.go index 8c601a17d1..f6b252759d 100644 --- a/builder/build.go +++ b/builder/build.go @@ -889,6 +889,14 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe return err } objs = []string{obj} + name, err := LookupCommand("llvm-objcopy") + if err != nil { + return err + } + err = exec.Command(name, "--strip-symbol", "call_start_cpu0", obj).Run() + if err != nil { + return err + } } result.Binary = result.Executable + ".a" f, err := os.Create(result.Binary) diff --git a/builder/commands.go b/builder/commands.go index d804ee1476..89047a8b54 100644 --- a/builder/commands.go +++ b/builder/commands.go @@ -22,6 +22,7 @@ func init() { commands["ld.lld"] = []string{"ld.lld-" + llvmMajor, "ld.lld"} commands["wasm-ld"] = []string{"wasm-ld-" + llvmMajor, "wasm-ld"} commands["lldb"] = []string{"lldb-" + llvmMajor, "lldb"} + commands["llvm-objcopy"] = []string{"llvm-objcopy-" + llvmMajor, "llvm-objcopy"} // Add the path to a Homebrew-installed LLVM for ease of use (no need to // manually set $PATH). if runtime.GOOS == "darwin" { @@ -39,6 +40,7 @@ func init() { commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld") commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld") commands["lldb"] = append(commands["lldb"], prefix+"lldb") + commands["llvm-objcopy"] = append(commands["llvm-objcopy"], prefix+"llvm-objcopy") } // Add the path for when LLVM was installed with the installer from // llvm.org, which by default doesn't add LLVM to the $PATH environment From f87b75dd0a7c7aaa934116d8f4f698da3d19da8f Mon Sep 17 00:00:00 2001 From: Hector Chu Date: Thu, 16 Apr 2026 19:34:35 +0100 Subject: [PATCH 10/30] allow c side to set gc vars --- builder/build.go | 4 ---- src/runtime/baremetal.go | 10 +++++++++- src/runtime/scheduler_none.go | 9 --------- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/builder/build.go b/builder/build.go index f6b252759d..050609b6de 100644 --- a/builder/build.go +++ b/builder/build.go @@ -853,10 +853,6 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe } if config.Triple() == "xtensa" { for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { - switch fn.Name() { - case "malloc", "calloc", "free": - fn.SetLinkage(llvm.InternalLinkage) - } if strings.HasPrefix(fn.Name(), "__atomic_") || strings.HasPrefix(fn.Name(), "__sync_") { fn.SetLinkage(llvm.InternalLinkage) diff --git a/src/runtime/baremetal.go b/src/runtime/baremetal.go index c42787197b..bce1fd7e84 100644 --- a/src/runtime/baremetal.go +++ b/src/runtime/baremetal.go @@ -30,7 +30,15 @@ var ( stackTop = uintptr(unsafe.Pointer(&stackTopSymbol)) ) -func allocateHeap() {} +//export tinygo_init +func tinygo_init(p, n, bss, bssEnd, sp uintptr) { + heapStart, heapEnd = p, p+n + globalsStart, globalsEnd = bss, bssEnd + stackTop = sp + initRand() + initHeap() + initAll() +} // growHeap tries to grow the heap size. It returns true if it succeeds, false // otherwise. diff --git a/src/runtime/scheduler_none.go b/src/runtime/scheduler_none.go index 1091e543be..7414172737 100644 --- a/src/runtime/scheduler_none.go +++ b/src/runtime/scheduler_none.go @@ -28,15 +28,6 @@ func run() { mainExited = true } -//export tinygo_init -func tinygo_init() { - allocateHeap() - stackTop = getCurrentStackPointer() - initRand() - initHeap() - initAll() -} - //go:linkname sleep time.Sleep func sleep(duration int64) { if duration <= 0 { From ead5efa719df18825c9242449699f308a6e6eda7 Mon Sep 17 00:00:00 2001 From: Hector Chu Date: Thu, 16 Apr 2026 21:12:08 +0100 Subject: [PATCH 11/30] fix error --- builder/build.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/build.go b/builder/build.go index 050609b6de..6d171c746f 100644 --- a/builder/build.go +++ b/builder/build.go @@ -836,7 +836,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe mod = ctx.NewModule("main") for _, dependency := range job.dependencies { if strings.HasSuffix(dependency.description, ".S") || - dependency.description == "" { + strings.HasSuffix(dependency.result, ".a") { continue } depMod, err := ctx.ParseBitcodeFile(dependency.result) From 1728ce0384a540cc8fa72c7a925fd4896a31777b Mon Sep 17 00:00:00 2001 From: Hector Chu Date: Fri, 17 Apr 2026 11:10:06 +0100 Subject: [PATCH 12/30] remove malloc and friends --- builder/build.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/builder/build.go b/builder/build.go index 6d171c746f..214a28edb0 100644 --- a/builder/build.go +++ b/builder/build.go @@ -851,6 +851,15 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe if fn := mod.NamedFunction("main"); !fn.IsNil() { fn.EraseFromParentAsFunction() } + for _, name := range []string{"malloc", "calloc", "free"} { + if fn := mod.NamedFunction(name); !fn.IsNil() { + fn2 := llvm.AddFunction(mod, "__"+name, fn.Type()) + fn2.SetLinkage(llvm.ExternalLinkage) + fn.ReplaceAllUsesWith(fn2) + fn.EraseFromParentAsFunction() + fn2.SetName(name) + } + } if config.Triple() == "xtensa" { for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { if strings.HasPrefix(fn.Name(), "__atomic_") || From ccb331e5134358bf3dcf99fa0c4bd8152372328b Mon Sep 17 00:00:00 2001 From: Hector Chu Date: Fri, 17 Apr 2026 11:16:22 +0100 Subject: [PATCH 13/30] remove unnecessary line --- builder/build.go | 1 - 1 file changed, 1 deletion(-) diff --git a/builder/build.go b/builder/build.go index 214a28edb0..731efef396 100644 --- a/builder/build.go +++ b/builder/build.go @@ -854,7 +854,6 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe for _, name := range []string{"malloc", "calloc", "free"} { if fn := mod.NamedFunction(name); !fn.IsNil() { fn2 := llvm.AddFunction(mod, "__"+name, fn.Type()) - fn2.SetLinkage(llvm.ExternalLinkage) fn.ReplaceAllUsesWith(fn2) fn.EraseFromParentAsFunction() fn2.SetName(name) From 575886c68f7bde3f1903f1cca2e1dd4c9cca0381 Mon Sep 17 00:00:00 2001 From: Hector Chu Date: Fri, 17 Apr 2026 11:39:28 +0100 Subject: [PATCH 14/30] simplify --- builder/build.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/build.go b/builder/build.go index 731efef396..f82db35cb0 100644 --- a/builder/build.go +++ b/builder/build.go @@ -853,7 +853,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe } for _, name := range []string{"malloc", "calloc", "free"} { if fn := mod.NamedFunction(name); !fn.IsNil() { - fn2 := llvm.AddFunction(mod, "__"+name, fn.Type()) + fn2 := llvm.AddFunction(mod, name, fn.Type()) fn.ReplaceAllUsesWith(fn2) fn.EraseFromParentAsFunction() fn2.SetName(name) From 0ec59dfd905e749aa7ab955d1cbba1ded4b0ce3d Mon Sep 17 00:00:00 2001 From: Hector Chu Date: Fri, 17 Apr 2026 11:51:41 +0100 Subject: [PATCH 15/30] simplify --- builder/build.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/builder/build.go b/builder/build.go index f82db35cb0..8af3371cce 100644 --- a/builder/build.go +++ b/builder/build.go @@ -848,10 +848,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe return err } } - if fn := mod.NamedFunction("main"); !fn.IsNil() { - fn.EraseFromParentAsFunction() - } - for _, name := range []string{"malloc", "calloc", "free"} { + for _, name := range []string{"main", "malloc", "calloc", "free"} { if fn := mod.NamedFunction(name); !fn.IsNil() { fn2 := llvm.AddFunction(mod, name, fn.Type()) fn.ReplaceAllUsesWith(fn2) From 27e364f31e0b27f6e96b60a07e91c01c3151a7ae Mon Sep 17 00:00:00 2001 From: Hector Chu Date: Fri, 17 Apr 2026 14:40:38 +0100 Subject: [PATCH 16/30] revert unneeded macos change --- compileopts/target.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/compileopts/target.go b/compileopts/target.go index 43ec36004b..4e0269e6b0 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -398,9 +398,7 @@ func defaultTarget(options *Options) (*TargetSpec, error) { llvmvendor := "unknown" switch options.GOOS { case "darwin": - if options.GC == "boehm" { - spec.GC = "boehm" - } + spec.GC = "boehm" platformVersion := "10.12.0" if options.GOARCH == "arm64" { platformVersion = "11.0.0" // first macosx platform with arm64 support @@ -420,13 +418,10 @@ func defaultTarget(options *Options) (*TargetSpec, error) { ) spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/futex/futex_darwin.c", + "src/internal/task/task_threads.c", "src/runtime/os_darwin.c", "src/runtime/runtime_unix.c", "src/runtime/signal.c") - if options.Scheduler == "threads" { - spec.ExtraFiles = append(spec.ExtraFiles, - "src/internal/task/task_threads.c") - } case "linux": spec.GC = "boehm" spec.Scheduler = "threads" From fb26e5dd2b41272c66dc5b3d0f9b25183d5477ab Mon Sep 17 00:00:00 2001 From: Hector Chu Date: Sat, 18 Apr 2026 00:30:14 +0100 Subject: [PATCH 17/30] apply fixup to riscv as well --- builder/build.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/builder/build.go b/builder/build.go index 8af3371cce..7228ae2b31 100644 --- a/builder/build.go +++ b/builder/build.go @@ -856,12 +856,12 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe fn2.SetName(name) } } - if config.Triple() == "xtensa" { - for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { - if strings.HasPrefix(fn.Name(), "__atomic_") || - strings.HasPrefix(fn.Name(), "__sync_") { - fn.SetLinkage(llvm.InternalLinkage) - } + for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { + if strings.HasPrefix(fn.Name(), "__atomic_") || + strings.HasPrefix(fn.Name(), "__sync_") { + fn.SetLinkage(llvm.InternalLinkage) + } + if config.Triple() == "xtensa" { fn.SetSection(".text." + fn.Name()) if fn.Name() == "tinygo_scanstack" { fn.SetSection(".text.tinygo_scanCurrentStack") From bfa8cd7afc77f72b3fe1c3d20cf82e8bc2c21cd1 Mon Sep 17 00:00:00 2001 From: Hector Chu Date: Sat, 18 Apr 2026 22:08:59 +0100 Subject: [PATCH 18/30] remove nonnull attribute from malloc --- src/runtime/baremetal.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/runtime/baremetal.go b/src/runtime/baremetal.go index bce1fd7e84..b1ba24be28 100644 --- a/src/runtime/baremetal.go +++ b/src/runtime/baremetal.go @@ -49,6 +49,9 @@ func growHeap() bool { //export malloc func libc_malloc(size uintptr) unsafe.Pointer { + if size == ^uintptr(0) { + return nil + } // Note: this zeroes the returned buffer which is not necessary. // The same goes for bytealg.MakeNoZero. return alloc(size, nil) From ad2cb23050320ea287fb0515f002b4591156326a Mon Sep 17 00:00:00 2001 From: Hector Chu Date: Sun, 19 Apr 2026 12:42:09 +0100 Subject: [PATCH 19/30] add buildmode to tags --- builder/build.go | 16 ++-------------- compileopts/config.go | 1 + src/runtime/atomics_critical.go | 2 +- src/runtime/baremetal.go | 25 ++----------------------- src/runtime/baremetal_malloc.go | 23 +++++++++++++++++++++++ 5 files changed, 29 insertions(+), 38 deletions(-) create mode 100644 src/runtime/baremetal_malloc.go diff --git a/builder/build.go b/builder/build.go index 7228ae2b31..f5bb063d57 100644 --- a/builder/build.go +++ b/builder/build.go @@ -848,20 +848,8 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe return err } } - for _, name := range []string{"main", "malloc", "calloc", "free"} { - if fn := mod.NamedFunction(name); !fn.IsNil() { - fn2 := llvm.AddFunction(mod, name, fn.Type()) - fn.ReplaceAllUsesWith(fn2) - fn.EraseFromParentAsFunction() - fn2.SetName(name) - } - } - for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { - if strings.HasPrefix(fn.Name(), "__atomic_") || - strings.HasPrefix(fn.Name(), "__sync_") { - fn.SetLinkage(llvm.InternalLinkage) - } - if config.Triple() == "xtensa" { + if config.Triple() == "xtensa" { + for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { fn.SetSection(".text." + fn.Name()) if fn.Name() == "tinygo_scanstack" { fn.SetSection(".text.tinygo_scanCurrentStack") diff --git a/compileopts/config.go b/compileopts/config.go index 639f577f54..bb7935f065 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -110,6 +110,7 @@ func (c *Config) BuildTags() []string { "osusergo", // to get os/user to work "math_big_pure_go", // to get math/big to work "gc." + c.GC(), "scheduler." + c.Scheduler(), // used inside the runtime package + "buildmode." + strings.ReplaceAll(c.BuildMode(), "-", "_"), "serial." + c.Serial()}...) // used inside the machine package switch c.Scheduler() { case "threads", "cores": diff --git a/src/runtime/atomics_critical.go b/src/runtime/atomics_critical.go index 74ce321f10..d62cc562a5 100644 --- a/src/runtime/atomics_critical.go +++ b/src/runtime/atomics_critical.go @@ -1,4 +1,4 @@ -//go:build baremetal && !tinygo.wasm +//go:build baremetal && !tinygo.wasm && !buildmode.c_archive // Automatically generated file. DO NOT EDIT. // This file implements standins for non-native atomics using critical sections. diff --git a/src/runtime/baremetal.go b/src/runtime/baremetal.go index b1ba24be28..7e673e60d9 100644 --- a/src/runtime/baremetal.go +++ b/src/runtime/baremetal.go @@ -31,9 +31,9 @@ var ( ) //export tinygo_init -func tinygo_init(p, n, bss, bssEnd, sp uintptr) { +func tinygo_init(p, n, glb, glbEnd, sp uintptr) { heapStart, heapEnd = p, p+n - globalsStart, globalsEnd = bss, bssEnd + globalsStart, globalsEnd = glb, glbEnd stackTop = sp initRand() initHeap() @@ -47,27 +47,6 @@ func growHeap() bool { return false } -//export malloc -func libc_malloc(size uintptr) unsafe.Pointer { - if size == ^uintptr(0) { - return nil - } - // Note: this zeroes the returned buffer which is not necessary. - // The same goes for bytealg.MakeNoZero. - return alloc(size, nil) -} - -//export calloc -func libc_calloc(nmemb, size uintptr) unsafe.Pointer { - // No difference between calloc and malloc. - return libc_malloc(nmemb * size) -} - -//export free -func libc_free(ptr unsafe.Pointer) { - free(ptr) -} - //export runtime_putchar func runtime_putchar(c byte) { putchar(c) diff --git a/src/runtime/baremetal_malloc.go b/src/runtime/baremetal_malloc.go new file mode 100644 index 0000000000..1f2e23fad7 --- /dev/null +++ b/src/runtime/baremetal_malloc.go @@ -0,0 +1,23 @@ +//go:build baremetal && !buildmode.c_archive + +package runtime + +import "unsafe" + +//export malloc +func libc_malloc(size uintptr) unsafe.Pointer { + // Note: this zeroes the returned buffer which is not necessary. + // The same goes for bytealg.MakeNoZero. + return alloc(size, nil) +} + +//export calloc +func libc_calloc(nmemb, size uintptr) unsafe.Pointer { + // No difference between calloc and malloc. + return libc_malloc(nmemb * size) +} + +//export free +func libc_free(ptr unsafe.Pointer) { + free(ptr) +} From 73291c88cd90e1674b46f6b9ccc00cecbb0c9a08 Mon Sep 17 00:00:00 2001 From: Hector Chu Date: Sun, 19 Apr 2026 12:58:53 +0100 Subject: [PATCH 20/30] doesn't seem necessary anymore --- builder/build.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/builder/build.go b/builder/build.go index f5bb063d57..567b476fe5 100644 --- a/builder/build.go +++ b/builder/build.go @@ -851,9 +851,6 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe if config.Triple() == "xtensa" { for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { fn.SetSection(".text." + fn.Name()) - if fn.Name() == "tinygo_scanstack" { - fn.SetSection(".text.tinygo_scanCurrentStack") - } } } buf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile) From 4126133c0ea962cc8e3ae808e4bd79453d004544 Mon Sep 17 00:00:00 2001 From: Hector Chu Date: Sun, 19 Apr 2026 13:45:54 +0100 Subject: [PATCH 21/30] better c-archive separation --- src/runtime/baremetal.go | 38 +--------------------- src/runtime/baremetal_c_archive.go | 23 ++++++++++++++ src/runtime/baremetal_malloc.go | 23 -------------- src/runtime/baremetal_no_c_archive.go | 46 +++++++++++++++++++++++++++ 4 files changed, 70 insertions(+), 60 deletions(-) create mode 100644 src/runtime/baremetal_c_archive.go delete mode 100644 src/runtime/baremetal_malloc.go create mode 100644 src/runtime/baremetal_no_c_archive.go diff --git a/src/runtime/baremetal.go b/src/runtime/baremetal.go index 7e673e60d9..96d6721329 100644 --- a/src/runtime/baremetal.go +++ b/src/runtime/baremetal.go @@ -2,43 +2,7 @@ package runtime -import ( - "sync/atomic" - "unsafe" -) - -//go:extern _heap_start -var heapStartSymbol [0]byte - -//go:extern _heap_end -var heapEndSymbol [0]byte - -//go:extern _globals_start -var globalsStartSymbol [0]byte - -//go:extern _globals_end -var globalsEndSymbol [0]byte - -//go:extern _stack_top -var stackTopSymbol [0]byte - -var ( - heapStart = uintptr(unsafe.Pointer(&heapStartSymbol)) - heapEnd = uintptr(unsafe.Pointer(&heapEndSymbol)) - globalsStart = uintptr(unsafe.Pointer(&globalsStartSymbol)) - globalsEnd = uintptr(unsafe.Pointer(&globalsEndSymbol)) - stackTop = uintptr(unsafe.Pointer(&stackTopSymbol)) -) - -//export tinygo_init -func tinygo_init(p, n, glb, glbEnd, sp uintptr) { - heapStart, heapEnd = p, p+n - globalsStart, globalsEnd = glb, glbEnd - stackTop = sp - initRand() - initHeap() - initAll() -} +import "sync/atomic" // growHeap tries to grow the heap size. It returns true if it succeeds, false // otherwise. diff --git a/src/runtime/baremetal_c_archive.go b/src/runtime/baremetal_c_archive.go new file mode 100644 index 0000000000..d3d668f009 --- /dev/null +++ b/src/runtime/baremetal_c_archive.go @@ -0,0 +1,23 @@ +//go:build baremetal && buildmode.c_archive + +package runtime + +var ( + heapStart uintptr + heapEnd uintptr + globalsStart uintptr + globalsEnd uintptr + stackTop uintptr +) + +// Allows C consumers of the library to set the GC variables. +// +//export tinygo_init +func tinygo_init(heap, heapSize, glob, globEnd, stack uintptr) { + heapStart, heapEnd = heap, heap+heapSize + globalsStart, globalsEnd = glob, globEnd + stackTop = stack + initRand() + initHeap() + initAll() +} diff --git a/src/runtime/baremetal_malloc.go b/src/runtime/baremetal_malloc.go deleted file mode 100644 index 1f2e23fad7..0000000000 --- a/src/runtime/baremetal_malloc.go +++ /dev/null @@ -1,23 +0,0 @@ -//go:build baremetal && !buildmode.c_archive - -package runtime - -import "unsafe" - -//export malloc -func libc_malloc(size uintptr) unsafe.Pointer { - // Note: this zeroes the returned buffer which is not necessary. - // The same goes for bytealg.MakeNoZero. - return alloc(size, nil) -} - -//export calloc -func libc_calloc(nmemb, size uintptr) unsafe.Pointer { - // No difference between calloc and malloc. - return libc_malloc(nmemb * size) -} - -//export free -func libc_free(ptr unsafe.Pointer) { - free(ptr) -} diff --git a/src/runtime/baremetal_no_c_archive.go b/src/runtime/baremetal_no_c_archive.go new file mode 100644 index 0000000000..41d012da11 --- /dev/null +++ b/src/runtime/baremetal_no_c_archive.go @@ -0,0 +1,46 @@ +//go:build baremetal && !buildmode.c_archive + +package runtime + +import "unsafe" + +//go:extern _heap_start +var heapStartSymbol [0]byte + +//go:extern _heap_end +var heapEndSymbol [0]byte + +//go:extern _globals_start +var globalsStartSymbol [0]byte + +//go:extern _globals_end +var globalsEndSymbol [0]byte + +//go:extern _stack_top +var stackTopSymbol [0]byte + +var ( + heapStart = uintptr(unsafe.Pointer(&heapStartSymbol)) + heapEnd = uintptr(unsafe.Pointer(&heapEndSymbol)) + globalsStart = uintptr(unsafe.Pointer(&globalsStartSymbol)) + globalsEnd = uintptr(unsafe.Pointer(&globalsEndSymbol)) + stackTop = uintptr(unsafe.Pointer(&stackTopSymbol)) +) + +//export malloc +func libc_malloc(size uintptr) unsafe.Pointer { + // Note: this zeroes the returned buffer which is not necessary. + // The same goes for bytealg.MakeNoZero. + return alloc(size, nil) +} + +//export calloc +func libc_calloc(nmemb, size uintptr) unsafe.Pointer { + // No difference between calloc and malloc. + return libc_malloc(nmemb * size) +} + +//export free +func libc_free(ptr unsafe.Pointer) { + free(ptr) +} From 3c2feba84f68f3dd45acc708374b85fd584d6ff4 Mon Sep 17 00:00:00 2001 From: Hector Chu Date: Sun, 19 Apr 2026 18:27:48 +0100 Subject: [PATCH 22/30] complete overhaul --- builder/build.go | 80 +--- builder/commands.go | 2 - compileopts/config.go | 12 +- src/device/esp/esp32.S | 52 --- src/device/esp/esp32_no_c_archive.S | 51 +++ .../esp/{esp32c3.S => esp32c3_no_c_archive.S} | 0 src/device/esp/esp32s3.S | 345 ------------------ src/device/esp/esp32s3_no_c_archive.S | 344 +++++++++++++++++ targets/esp32.json | 1 + targets/esp32c3.json | 2 +- targets/esp32s3.json | 1 + 11 files changed, 423 insertions(+), 467 deletions(-) create mode 100644 src/device/esp/esp32_no_c_archive.S rename src/device/esp/{esp32c3.S => esp32c3_no_c_archive.S} (100%) create mode 100644 src/device/esp/esp32s3_no_c_archive.S diff --git a/builder/build.go b/builder/build.go index 567b476fe5..64eabceec5 100644 --- a/builder/build.go +++ b/builder/build.go @@ -705,6 +705,10 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe result.Binary = result.Executable // final file ldflags := append(config.LDFlags(), "-o", result.Executable) + if config.Options.BuildMode == "c-archive" { + ldflags = []string{"-r", "-o", result.Executable} + } + if config.Options.BuildMode == "c-shared" { if !strings.HasPrefix(config.Triple(), "wasm32-") { return result, fmt.Errorf("buildmode c-shared is only supported on wasm at the moment") @@ -724,7 +728,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe // Add compiler-rt dependency if needed. Usually this is a simple load from // a cache. - if config.Target.RTLib == "compiler-rt" { + if config.Target.RTLib == "compiler-rt" && config.Options.BuildMode != "c-archive" { job, unlock, err := libCompilerRT.load(config, tmpdir) if err != nil { return result, err @@ -785,7 +789,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe } // Add libc dependencies, if they exist. - if config.BuildMode() != "c-archive" { + if config.Options.BuildMode != "c-archive" { linkerDependencies = append(linkerDependencies, libcDependencies...) } @@ -830,69 +834,6 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe } ldflags = append(ldflags, dependency.result) } - - if config.BuildMode() == "c-archive" { - ctx := llvm.NewContext() - mod = ctx.NewModule("main") - for _, dependency := range job.dependencies { - if strings.HasSuffix(dependency.description, ".S") || - strings.HasSuffix(dependency.result, ".a") { - continue - } - depMod, err := ctx.ParseBitcodeFile(dependency.result) - if err != nil { - return err - } - err = llvm.LinkModules(mod, depMod) - if err != nil { - return err - } - } - if config.Triple() == "xtensa" { - for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { - fn.SetSection(".text." + fn.Name()) - } - } - buf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile) - if err != nil { - return err - } - defer buf.Dispose() - err = os.WriteFile(result.Executable, buf.Bytes(), 0666) - if err != nil { - return err - } - objs := []string{result.Executable} - for _, dependency := range job.dependencies { - if strings.HasSuffix(dependency.description, ".S") { - objs = append(objs, dependency.result) - } - } - if config.Triple() == "xtensa" { - obj := result.Executable + ".o" - err = link(config.Target.Linker, append(objs, "-r", "-o", obj)...) - if err != nil { - return err - } - objs = []string{obj} - name, err := LookupCommand("llvm-objcopy") - if err != nil { - return err - } - err = exec.Command(name, "--strip-symbol", "call_start_cpu0", obj).Run() - if err != nil { - return err - } - } - result.Binary = result.Executable + ".a" - f, err := os.Create(result.Binary) - if err != nil { - return err - } - defer f.Close() - return makeArchive(f, objs) - } - ldflags = append(ldflags, "-mllvm", "-mcpu="+config.CPU()) ldflags = append(ldflags, "-mllvm", "-mattr="+config.Features()) // needed for MIPS softfloat if config.GOOS() == "windows" { @@ -930,6 +871,15 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe if err != nil { return err } + if config.Options.BuildMode == "c-archive" { + result.Binary = result.Executable + ".a" + f, err := os.Create(result.Binary) + if err != nil { + return err + } + defer f.Close() + return makeArchive(f, []string{result.Executable}) + } var calculatedStacks []string var stackSizes map[string]functionStackSize diff --git a/builder/commands.go b/builder/commands.go index 89047a8b54..d804ee1476 100644 --- a/builder/commands.go +++ b/builder/commands.go @@ -22,7 +22,6 @@ func init() { commands["ld.lld"] = []string{"ld.lld-" + llvmMajor, "ld.lld"} commands["wasm-ld"] = []string{"wasm-ld-" + llvmMajor, "wasm-ld"} commands["lldb"] = []string{"lldb-" + llvmMajor, "lldb"} - commands["llvm-objcopy"] = []string{"llvm-objcopy-" + llvmMajor, "llvm-objcopy"} // Add the path to a Homebrew-installed LLVM for ease of use (no need to // manually set $PATH). if runtime.GOOS == "darwin" { @@ -40,7 +39,6 @@ func init() { commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld") commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld") commands["lldb"] = append(commands["lldb"], prefix+"lldb") - commands["llvm-objcopy"] = append(commands["llvm-objcopy"], prefix+"llvm-objcopy") } // Add the path for when LLVM was installed with the installer from // llvm.org, which by default doesn't add LLVM to the $PATH environment diff --git a/compileopts/config.go b/compileopts/config.go index bb7935f065..7a44e853c1 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -472,8 +472,16 @@ func (c *Config) LDFlags() []string { // ExtraFiles returns the list of extra files to be built and linked with the // executable. This can include extra C and assembly files. -func (c *Config) ExtraFiles() []string { - return c.Target.ExtraFiles +func (c *Config) ExtraFiles() (paths []string) { + for _, path := range c.Target.ExtraFiles { + if c.Options.BuildMode == "c-archive" { + if strings.HasSuffix(path, "_no_c_archive"+filepath.Ext(path)) { + continue + } + } + paths = append(paths, path) + } + return } // DumpSSA returns whether to dump Go SSA while compiling (-dumpssa flag). Only diff --git a/src/device/esp/esp32.S b/src/device/esp/esp32.S index 1179a2daa1..f4fe560bc7 100644 --- a/src/device/esp/esp32.S +++ b/src/device/esp/esp32.S @@ -1,55 +1,3 @@ - -// The following definitions were copied from: -// esp-idf/components/xtensa/include/xtensa/corebits.h -#define PS_WOE_MASK 0x00040000 -#define PS_OWB_MASK 0x00000F00 -#define PS_CALLINC_MASK 0x00030000 -#define PS_WOE PS_WOE_MASK - -// Only calling it call_start_cpu0 for consistency with ESP-IDF. -.section .text.call_start_cpu0 -1: - .long _stack_top -.global call_start_cpu0 -call_start_cpu0: - // We need to set the stack pointer to a different value. This is somewhat - // complicated in the Xtensa architecture. The code below is a modified - // version of the following code: - // https://github.com/espressif/esp-idf/blob/c77c4ccf/components/xtensa/include/xt_instr_macros.h#L47 - - // Disable WOE. - rsr.ps a2 - movi a3, ~(PS_WOE_MASK) - and a2, a2, a3 - wsr.ps a2 - rsync - - // Set WINDOWSTART to 1 << WINDOWBASE. - rsr.windowbase a2 - ssl a2 - movi a2, 1 - sll a2, a2 - wsr.windowstart a2 - rsync - - // Load new stack pointer. - l32r sp, 1b - - // Re-enable WOE. - rsr.ps a2 - movi a3, PS_WOE - or a2, a2, a3 - wsr.ps a2 - rsync - - // Enable the FPU (coprocessor 0 so the lowest bit). - movi a2, 1 - wsr.cpenable a2 - rsync - - // Jump to the runtime start function written in Go. - call4 main - .section .text.tinygo_scanCurrentStack .global tinygo_scanCurrentStack tinygo_scanCurrentStack: diff --git a/src/device/esp/esp32_no_c_archive.S b/src/device/esp/esp32_no_c_archive.S new file mode 100644 index 0000000000..c8497779fc --- /dev/null +++ b/src/device/esp/esp32_no_c_archive.S @@ -0,0 +1,51 @@ + +// The following definitions were copied from: +// esp-idf/components/xtensa/include/xtensa/corebits.h +#define PS_WOE_MASK 0x00040000 +#define PS_OWB_MASK 0x00000F00 +#define PS_CALLINC_MASK 0x00030000 +#define PS_WOE PS_WOE_MASK + +// Only calling it call_start_cpu0 for consistency with ESP-IDF. +.section .text.call_start_cpu0 +1: + .long _stack_top +.global call_start_cpu0 +call_start_cpu0: + // We need to set the stack pointer to a different value. This is somewhat + // complicated in the Xtensa architecture. The code below is a modified + // version of the following code: + // https://github.com/espressif/esp-idf/blob/c77c4ccf/components/xtensa/include/xt_instr_macros.h#L47 + + // Disable WOE. + rsr.ps a2 + movi a3, ~(PS_WOE_MASK) + and a2, a2, a3 + wsr.ps a2 + rsync + + // Set WINDOWSTART to 1 << WINDOWBASE. + rsr.windowbase a2 + ssl a2 + movi a2, 1 + sll a2, a2 + wsr.windowstart a2 + rsync + + // Load new stack pointer. + l32r sp, 1b + + // Re-enable WOE. + rsr.ps a2 + movi a3, PS_WOE + or a2, a2, a3 + wsr.ps a2 + rsync + + // Enable the FPU (coprocessor 0 so the lowest bit). + movi a2, 1 + wsr.cpenable a2 + rsync + + // Jump to the runtime start function written in Go. + call4 main diff --git a/src/device/esp/esp32c3.S b/src/device/esp/esp32c3_no_c_archive.S similarity index 100% rename from src/device/esp/esp32c3.S rename to src/device/esp/esp32c3_no_c_archive.S diff --git a/src/device/esp/esp32s3.S b/src/device/esp/esp32s3.S index 6566e3f342..6d066836fa 100644 --- a/src/device/esp/esp32s3.S +++ b/src/device/esp/esp32s3.S @@ -1,348 +1,3 @@ -// Startup code for the ESP32-S3 (Xtensa LX7, windowed ABI). -// -// The ROM bootloader loads IRAM/DRAM segments into SRAM but does NOT -// configure flash cache/MMU. We must: -// 1. Set up the windowed-ABI register file and stack pointer. -// 2. Set VECBASE and clear PS.EXCM (needed for callx4 window overflows). -// 3. Disable watchdog timers. -// 4. Configure the flash cache and MMU so IROM/DROM are accessible. -// 5. Jump to runtime.main (in IROM). -// -// Cache/MMU init sequence (from NuttX esp_loader.c / ESP-IDF bootloader / esp-hal): -// Phase A — configure cache modes: -// a. rom_config_instruction_cache_mode(16KB, 8-way, 32B) -// b. rom_Cache_Suspend_DCache() -// c. rom_config_data_cache_mode(32KB, 8-way, 32B) -// d. Cache_Resume_DCache(0) -// Phase B — map flash pages: -// e. Disable caches -// f. Cache_MMU_Init() — reset all MMU entries to invalid -// g. Cache_Set_IDROM_MMU_Size() — set IROM/DROM entry split -// h. Write MMU entries mapping flash page 0 for IROM and DROM -// i. Clear bus-shut bits -// j. Enable caches + isync - -#define PS_WOE 0x00040000 - -// ----------------------------------------------------------------------- -// Boot entry point — placed in IRAM by the linker. -// ----------------------------------------------------------------------- -.section .text.call_start_cpu0 - .literal_position - .align 4 -.Lstack_top_addr: - .long _stack_top -.Lmain_addr: - .long main -.Lvector_table_addr: - .long _vector_table -// WDT register addresses -.Lwdt_key: - .long 0x50D83AA1 -.Lrtc_wdt_protect: - .long 0x600080B0 -.Lrtc_wdt_config0: - .long 0x60008098 -.Ltimg0_wdt_protect: - .long 0x6001F064 -.Ltimg0_wdt_config0: - .long 0x6001F048 -.Ltimg1_wdt_protect: - .long 0x60020064 -.Ltimg1_wdt_config0: - .long 0x60020048 -.Lswd_protect: - .long 0x600080B8 -.Lswd_key: - .long 0x8F1D312A -.Lswd_conf: - .long 0x600080B4 -.Lswd_disable: - .long 0x40000000 -// ROM function addresses (from ESP-IDF esp32s3.rom.ld) -.Lrom_config_icache: - .long 0x40001a1c -.Lrom_config_dcache: - .long 0x40001a28 -.Lrom_suspend_dcache: - .long 0x400018b4 -.LCache_Resume_DCache: - .long 0x400018c0 -.LCache_Disable_ICache: - .long 0x4000186c -.LCache_Disable_DCache: - .long 0x40001884 -.LCache_MMU_Init: - .long 0x40001998 -.LCache_Set_IDROM_MMU_Size: - .long 0x40001914 -.LCache_Enable_ICache: - .long 0x40001878 -.LCache_Enable_DCache: - .long 0x40001890 -// Cache/MMU register addresses -.Lmmu_table_base: - .long 0x600C5000 -.Licache_ctrl1_reg: - .long 0x600C4064 -.Ldcache_ctrl1_reg: - .long 0x600C4004 -// End-of-section symbols for multi-page MMU mapping. -.Lirom_end: - .long _irom_end -.Ldrom_end: - .long _drom_end -.Lirom_base: - .long 0x42000000 -.Ldrom_base: - .long 0x3C000000 - -.global call_start_cpu0 -call_start_cpu0: - - // ---- 1. Windowed-ABI register file setup ---- - - // Disable WOE so we can safely manipulate WINDOWSTART. - rsr.ps a2 - movi a3, ~(PS_WOE) - and a2, a2, a3 - wsr.ps a2 - rsync - - // Set WINDOWSTART to 1 << WINDOWBASE (mark only current window as valid). - rsr.windowbase a2 - ssl a2 - movi a2, 1 - sll a2, a2 - wsr.windowstart a2 - rsync - - // Load stack pointer. - l32r a1, .Lstack_top_addr - - // Re-enable WOE. - rsr.ps a2 - movi a3, PS_WOE - or a2, a2, a3 - wsr.ps a2 - rsync - - // Enable FPU (coprocessor 0). - movi a2, 1 - wsr.cpenable a2 - rsync - - // ---- 2. Disable all watchdog timers (IMMEDIATELY, before any delay) ---- - l32r a3, .Lwdt_key - movi a4, 0 - - // RTC WDT - l32r a2, .Lrtc_wdt_protect - memw - s32i a3, a2, 0 - l32r a5, .Lrtc_wdt_config0 - memw - s32i a4, a5, 0 - memw - s32i a4, a2, 0 - - // TIMG0 WDT - l32r a2, .Ltimg0_wdt_protect - memw - s32i a3, a2, 0 - l32r a5, .Ltimg0_wdt_config0 - memw - s32i a4, a5, 0 - memw - s32i a4, a2, 0 - - // TIMG1 WDT - l32r a2, .Ltimg1_wdt_protect - memw - s32i a3, a2, 0 - l32r a5, .Ltimg1_wdt_config0 - memw - s32i a4, a5, 0 - memw - s32i a4, a2, 0 - - // Super WDT - l32r a2, .Lswd_protect - l32r a3, .Lswd_key - memw - s32i a3, a2, 0 - l32r a5, .Lswd_conf - l32r a6, .Lswd_disable - memw - s32i a6, a5, 0 - memw - s32i a4, a2, 0 - - // ---- 3. Set VECBASE and clear PS.EXCM ---- - // VECBASE must be set before any callx4 so that window overflow - // exceptions (triggered by register window rotation) route to our - // handlers in IRAM, not the ROM's default vectors. - l32r a8, .Lvector_table_addr - wsr.vecbase a8 - rsync - - // Clear PS.EXCM (bit 4) and PS.INTLEVEL (bits 0-3). - // The ROM bootloader may leave EXCM=1; with EXCM set any callx4 - // window overflow would become a double exception. - // Set PS.UM (bit 5) so level-1 exceptions route to User vector. - rsr.ps a2 - movi a3, ~0x1F - and a2, a2, a3 - movi a3, 0x20 - or a2, a2, a3 - wsr.ps a2 - rsync - - // ---- 4. Configure flash cache and MMU ---- - // - // ROM function calls use callx4 (windowed ABI): - // a4 = target address (overwritten with return addr by call mechanism) - // a5 = stack pointer for callee (becomes callee's a1 via entry) - // a6 = first argument (becomes callee's a2) - // a7 = second argument (becomes callee's a3) - // a8 = third argument (becomes callee's a4) - // Registers a0-a3 are preserved across callx4; a4-a11 may be clobbered. - - // Phase A: Configure cache modes (required for cache hardware to function). - // Without this, the cache doesn't know its size/associativity/line-size - // and cannot service flash accesses. - - // 4a. Configure ICache mode: 16KB, 8-way, 32-byte line - movi a6, 0x4000 // cache_size = 16KB - movi a7, 8 // ways = 8 - movi a8, 32 // line_size = 32 - mov a5, a1 - l32r a4, .Lrom_config_icache - callx4 a4 - - // 4b. Suspend DCache before configuring it - mov a5, a1 - l32r a4, .Lrom_suspend_dcache - callx4 a4 - - // 4c. Configure DCache mode: 32KB, 8-way, 32-byte line - movi a6, 0x8000 // cache_size = 32KB - movi a7, 8 // ways = 8 - movi a8, 32 // line_size = 32 - mov a5, a1 - l32r a4, .Lrom_config_dcache - callx4 a4 - - // 4d. Resume DCache - movi a6, 0 - mov a5, a1 - l32r a4, .LCache_Resume_DCache - callx4 a4 - - // Phase B: Map flash pages into MMU. - - // 4e. Disable ICache - mov a5, a1 - l32r a4, .LCache_Disable_ICache - callx4 a4 - - // 4f. Disable DCache - mov a5, a1 - l32r a4, .LCache_Disable_DCache - callx4 a4 - - // 4g. Initialize MMU (resets all 512 entries to invalid = 0x4000) - mov a5, a1 - l32r a4, .LCache_MMU_Init - callx4 a4 - - // 4h. Set IDROM MMU size: even 256/256 split. - // Each entry is 4 bytes, so 256 entries = 0x400 bytes per region. - movi a6, 0x400 // irom_mmu_size (256 entries × 4 bytes) - movi a7, 0x400 // drom_mmu_size (256 entries × 4 bytes) - mov a5, a1 - l32r a4, .LCache_Set_IDROM_MMU_Size - callx4 a4 - - // 4i. Map flash pages for IROM and DROM using identity mapping. - // MMU table at 0x600C5000: entries 0-255 = ICache, 256-511 = DCache. - // Entry value N = flash page N (SOC_MMU_VALID = 0 on S3). - // Each 64KB page needs one 4-byte entry. - // - // IROM: map pages 0..N where N = (_irom_end - 0x42000000) >> 16 - // DROM: map pages 0..M where M = (_drom_end - 0x3C000000) >> 16 - - l32r a8, .Lmmu_table_base // a8 = 0x600C5000 - - // --- IROM pages --- - l32r a2, .Lirom_end // a2 = _irom_end (VMA in 0x42xxxxxx) - l32r a3, .Lirom_base // a3 = 0x42000000 - sub a2, a2, a3 // a2 = byte offset past IROM base - srli a2, a2, 16 // a2 = last page index - addi a2, a2, 1 // a2 = number of pages to map - movi a9, 0 // a9 = page counter (and entry value) - mov a10, a8 // a10 = current MMU entry pointer -.Lirom_loop: - s32i a9, a10, 0 - addi a9, a9, 1 - addi a10, a10, 4 - blt a9, a2, .Lirom_loop - - // --- DROM pages --- - l32r a2, .Ldrom_end // a2 = _drom_end (VMA in 0x3Cxxxxxx) - l32r a3, .Ldrom_base // a3 = 0x3C000000 - sub a2, a2, a3 // a2 = byte offset past DROM base - srli a2, a2, 16 // a2 = last page index - addi a2, a2, 1 // a2 = number of pages to map - movi a9, 0 // a9 = page counter (and entry value) - addmi a10, a8, 0x400 // a10 = 0x600C5400 (DCache entry 256) -.Ldrom_loop: - s32i a9, a10, 0 - addi a9, a9, 1 - addi a10, a10, 4 - blt a9, a2, .Ldrom_loop - memw - - // 4j. Clear bus-shut bits so core 0 can access ICache and DCache buses. - l32r a8, .Licache_ctrl1_reg // 0x600C4064 - movi a9, 0 - s32i a9, a8, 0 // Clear all ICACHE_CTRL1 shut bits - l32r a8, .Ldcache_ctrl1_reg // 0x600C4004 - s32i a9, a8, 0 // Clear all DCACHE_CTRL1 shut bits - memw - - // 4k. Enable ICache (arg: autoload = 0) - movi a6, 0 - mov a5, a1 - l32r a4, .LCache_Enable_ICache - callx4 a4 - - // 4l. Enable DCache (arg: autoload = 0) - movi a6, 0 - mov a5, a1 - l32r a4, .LCache_Enable_DCache - callx4 a4 - - // Flush instruction pipeline so new cache/MMU config takes effect. - isync - - // ---- 5. Jump to main (in IROM) ---- - // Re-clear PS.EXCM in case ROM calls changed processor state. - rsr.ps a2 - movi a3, ~0x1F - and a2, a2, a3 - movi a3, 0x20 - or a2, a2, a3 - wsr.ps a2 - rsync - - mov a5, a1 - l32r a4, .Lmain_addr - callx4 a4 - - // If main returns, loop forever. -1: j 1b - // ----------------------------------------------------------------------- // tinygo_scanCurrentStack — Spill all Xtensa register windows to the // stack, then call tinygo_scanstack(sp) so the conservative GC can diff --git a/src/device/esp/esp32s3_no_c_archive.S b/src/device/esp/esp32s3_no_c_archive.S new file mode 100644 index 0000000000..5572a3d484 --- /dev/null +++ b/src/device/esp/esp32s3_no_c_archive.S @@ -0,0 +1,344 @@ +// Startup code for the ESP32-S3 (Xtensa LX7, windowed ABI). +// +// The ROM bootloader loads IRAM/DRAM segments into SRAM but does NOT +// configure flash cache/MMU. We must: +// 1. Set up the windowed-ABI register file and stack pointer. +// 2. Set VECBASE and clear PS.EXCM (needed for callx4 window overflows). +// 3. Disable watchdog timers. +// 4. Configure the flash cache and MMU so IROM/DROM are accessible. +// 5. Jump to runtime.main (in IROM). +// +// Cache/MMU init sequence (from NuttX esp_loader.c / ESP-IDF bootloader / esp-hal): +// Phase A — configure cache modes: +// a. rom_config_instruction_cache_mode(16KB, 8-way, 32B) +// b. rom_Cache_Suspend_DCache() +// c. rom_config_data_cache_mode(32KB, 8-way, 32B) +// d. Cache_Resume_DCache(0) +// Phase B — map flash pages: +// e. Disable caches +// f. Cache_MMU_Init() — reset all MMU entries to invalid +// g. Cache_Set_IDROM_MMU_Size() — set IROM/DROM entry split +// h. Write MMU entries mapping flash page 0 for IROM and DROM +// i. Clear bus-shut bits +// j. Enable caches + isync + +#define PS_WOE 0x00040000 + +// ----------------------------------------------------------------------- +// Boot entry point — placed in IRAM by the linker. +// ----------------------------------------------------------------------- +.section .text.call_start_cpu0 + .literal_position + .align 4 +.Lstack_top_addr: + .long _stack_top +.Lmain_addr: + .long main +.Lvector_table_addr: + .long _vector_table +// WDT register addresses +.Lwdt_key: + .long 0x50D83AA1 +.Lrtc_wdt_protect: + .long 0x600080B0 +.Lrtc_wdt_config0: + .long 0x60008098 +.Ltimg0_wdt_protect: + .long 0x6001F064 +.Ltimg0_wdt_config0: + .long 0x6001F048 +.Ltimg1_wdt_protect: + .long 0x60020064 +.Ltimg1_wdt_config0: + .long 0x60020048 +.Lswd_protect: + .long 0x600080B8 +.Lswd_key: + .long 0x8F1D312A +.Lswd_conf: + .long 0x600080B4 +.Lswd_disable: + .long 0x40000000 +// ROM function addresses (from ESP-IDF esp32s3.rom.ld) +.Lrom_config_icache: + .long 0x40001a1c +.Lrom_config_dcache: + .long 0x40001a28 +.Lrom_suspend_dcache: + .long 0x400018b4 +.LCache_Resume_DCache: + .long 0x400018c0 +.LCache_Disable_ICache: + .long 0x4000186c +.LCache_Disable_DCache: + .long 0x40001884 +.LCache_MMU_Init: + .long 0x40001998 +.LCache_Set_IDROM_MMU_Size: + .long 0x40001914 +.LCache_Enable_ICache: + .long 0x40001878 +.LCache_Enable_DCache: + .long 0x40001890 +// Cache/MMU register addresses +.Lmmu_table_base: + .long 0x600C5000 +.Licache_ctrl1_reg: + .long 0x600C4064 +.Ldcache_ctrl1_reg: + .long 0x600C4004 +// End-of-section symbols for multi-page MMU mapping. +.Lirom_end: + .long _irom_end +.Ldrom_end: + .long _drom_end +.Lirom_base: + .long 0x42000000 +.Ldrom_base: + .long 0x3C000000 + +.global call_start_cpu0 +call_start_cpu0: + + // ---- 1. Windowed-ABI register file setup ---- + + // Disable WOE so we can safely manipulate WINDOWSTART. + rsr.ps a2 + movi a3, ~(PS_WOE) + and a2, a2, a3 + wsr.ps a2 + rsync + + // Set WINDOWSTART to 1 << WINDOWBASE (mark only current window as valid). + rsr.windowbase a2 + ssl a2 + movi a2, 1 + sll a2, a2 + wsr.windowstart a2 + rsync + + // Load stack pointer. + l32r a1, .Lstack_top_addr + + // Re-enable WOE. + rsr.ps a2 + movi a3, PS_WOE + or a2, a2, a3 + wsr.ps a2 + rsync + + // Enable FPU (coprocessor 0). + movi a2, 1 + wsr.cpenable a2 + rsync + + // ---- 2. Disable all watchdog timers (IMMEDIATELY, before any delay) ---- + l32r a3, .Lwdt_key + movi a4, 0 + + // RTC WDT + l32r a2, .Lrtc_wdt_protect + memw + s32i a3, a2, 0 + l32r a5, .Lrtc_wdt_config0 + memw + s32i a4, a5, 0 + memw + s32i a4, a2, 0 + + // TIMG0 WDT + l32r a2, .Ltimg0_wdt_protect + memw + s32i a3, a2, 0 + l32r a5, .Ltimg0_wdt_config0 + memw + s32i a4, a5, 0 + memw + s32i a4, a2, 0 + + // TIMG1 WDT + l32r a2, .Ltimg1_wdt_protect + memw + s32i a3, a2, 0 + l32r a5, .Ltimg1_wdt_config0 + memw + s32i a4, a5, 0 + memw + s32i a4, a2, 0 + + // Super WDT + l32r a2, .Lswd_protect + l32r a3, .Lswd_key + memw + s32i a3, a2, 0 + l32r a5, .Lswd_conf + l32r a6, .Lswd_disable + memw + s32i a6, a5, 0 + memw + s32i a4, a2, 0 + + // ---- 3. Set VECBASE and clear PS.EXCM ---- + // VECBASE must be set before any callx4 so that window overflow + // exceptions (triggered by register window rotation) route to our + // handlers in IRAM, not the ROM's default vectors. + l32r a8, .Lvector_table_addr + wsr.vecbase a8 + rsync + + // Clear PS.EXCM (bit 4) and PS.INTLEVEL (bits 0-3). + // The ROM bootloader may leave EXCM=1; with EXCM set any callx4 + // window overflow would become a double exception. + // Set PS.UM (bit 5) so level-1 exceptions route to User vector. + rsr.ps a2 + movi a3, ~0x1F + and a2, a2, a3 + movi a3, 0x20 + or a2, a2, a3 + wsr.ps a2 + rsync + + // ---- 4. Configure flash cache and MMU ---- + // + // ROM function calls use callx4 (windowed ABI): + // a4 = target address (overwritten with return addr by call mechanism) + // a5 = stack pointer for callee (becomes callee's a1 via entry) + // a6 = first argument (becomes callee's a2) + // a7 = second argument (becomes callee's a3) + // a8 = third argument (becomes callee's a4) + // Registers a0-a3 are preserved across callx4; a4-a11 may be clobbered. + + // Phase A: Configure cache modes (required for cache hardware to function). + // Without this, the cache doesn't know its size/associativity/line-size + // and cannot service flash accesses. + + // 4a. Configure ICache mode: 16KB, 8-way, 32-byte line + movi a6, 0x4000 // cache_size = 16KB + movi a7, 8 // ways = 8 + movi a8, 32 // line_size = 32 + mov a5, a1 + l32r a4, .Lrom_config_icache + callx4 a4 + + // 4b. Suspend DCache before configuring it + mov a5, a1 + l32r a4, .Lrom_suspend_dcache + callx4 a4 + + // 4c. Configure DCache mode: 32KB, 8-way, 32-byte line + movi a6, 0x8000 // cache_size = 32KB + movi a7, 8 // ways = 8 + movi a8, 32 // line_size = 32 + mov a5, a1 + l32r a4, .Lrom_config_dcache + callx4 a4 + + // 4d. Resume DCache + movi a6, 0 + mov a5, a1 + l32r a4, .LCache_Resume_DCache + callx4 a4 + + // Phase B: Map flash pages into MMU. + + // 4e. Disable ICache + mov a5, a1 + l32r a4, .LCache_Disable_ICache + callx4 a4 + + // 4f. Disable DCache + mov a5, a1 + l32r a4, .LCache_Disable_DCache + callx4 a4 + + // 4g. Initialize MMU (resets all 512 entries to invalid = 0x4000) + mov a5, a1 + l32r a4, .LCache_MMU_Init + callx4 a4 + + // 4h. Set IDROM MMU size: even 256/256 split. + // Each entry is 4 bytes, so 256 entries = 0x400 bytes per region. + movi a6, 0x400 // irom_mmu_size (256 entries × 4 bytes) + movi a7, 0x400 // drom_mmu_size (256 entries × 4 bytes) + mov a5, a1 + l32r a4, .LCache_Set_IDROM_MMU_Size + callx4 a4 + + // 4i. Map flash pages for IROM and DROM using identity mapping. + // MMU table at 0x600C5000: entries 0-255 = ICache, 256-511 = DCache. + // Entry value N = flash page N (SOC_MMU_VALID = 0 on S3). + // Each 64KB page needs one 4-byte entry. + // + // IROM: map pages 0..N where N = (_irom_end - 0x42000000) >> 16 + // DROM: map pages 0..M where M = (_drom_end - 0x3C000000) >> 16 + + l32r a8, .Lmmu_table_base // a8 = 0x600C5000 + + // --- IROM pages --- + l32r a2, .Lirom_end // a2 = _irom_end (VMA in 0x42xxxxxx) + l32r a3, .Lirom_base // a3 = 0x42000000 + sub a2, a2, a3 // a2 = byte offset past IROM base + srli a2, a2, 16 // a2 = last page index + addi a2, a2, 1 // a2 = number of pages to map + movi a9, 0 // a9 = page counter (and entry value) + mov a10, a8 // a10 = current MMU entry pointer +.Lirom_loop: + s32i a9, a10, 0 + addi a9, a9, 1 + addi a10, a10, 4 + blt a9, a2, .Lirom_loop + + // --- DROM pages --- + l32r a2, .Ldrom_end // a2 = _drom_end (VMA in 0x3Cxxxxxx) + l32r a3, .Ldrom_base // a3 = 0x3C000000 + sub a2, a2, a3 // a2 = byte offset past DROM base + srli a2, a2, 16 // a2 = last page index + addi a2, a2, 1 // a2 = number of pages to map + movi a9, 0 // a9 = page counter (and entry value) + addmi a10, a8, 0x400 // a10 = 0x600C5400 (DCache entry 256) +.Ldrom_loop: + s32i a9, a10, 0 + addi a9, a9, 1 + addi a10, a10, 4 + blt a9, a2, .Ldrom_loop + memw + + // 4j. Clear bus-shut bits so core 0 can access ICache and DCache buses. + l32r a8, .Licache_ctrl1_reg // 0x600C4064 + movi a9, 0 + s32i a9, a8, 0 // Clear all ICACHE_CTRL1 shut bits + l32r a8, .Ldcache_ctrl1_reg // 0x600C4004 + s32i a9, a8, 0 // Clear all DCACHE_CTRL1 shut bits + memw + + // 4k. Enable ICache (arg: autoload = 0) + movi a6, 0 + mov a5, a1 + l32r a4, .LCache_Enable_ICache + callx4 a4 + + // 4l. Enable DCache (arg: autoload = 0) + movi a6, 0 + mov a5, a1 + l32r a4, .LCache_Enable_DCache + callx4 a4 + + // Flush instruction pipeline so new cache/MMU config takes effect. + isync + + // ---- 5. Jump to main (in IROM) ---- + // Re-clear PS.EXCM in case ROM calls changed processor state. + rsr.ps a2 + movi a3, ~0x1F + and a2, a2, a3 + movi a3, 0x20 + or a2, a2, a3 + wsr.ps a2 + rsync + + mov a5, a1 + l32r a4, .Lmain_addr + callx4 a4 + + // If main returns, loop forever. +1: j 1b diff --git a/targets/esp32.json b/targets/esp32.json index 2c7abd6993..c87568cd0e 100644 --- a/targets/esp32.json +++ b/targets/esp32.json @@ -13,6 +13,7 @@ "linkerscript": "targets/esp32.ld", "extra-files": [ "src/device/esp/esp32.S", + "src/device/esp/esp32_no_c_archive.S", "src/internal/task/task_stack_esp32.S" ], "binary-format": "esp32", diff --git a/targets/esp32c3.json b/targets/esp32c3.json index 583506c19f..0e8817c9ca 100644 --- a/targets/esp32c3.json +++ b/targets/esp32c3.json @@ -12,7 +12,7 @@ ], "linkerscript": "targets/esp32c3.ld", "extra-files": [ - "src/device/esp/esp32c3.S" + "src/device/esp/esp32c3_no_c_archive.S" ], "binary-format": "esp32c3", "flash-method": "esp32jtag", diff --git a/targets/esp32s3.json b/targets/esp32s3.json index ea6527d6d5..109664d0e0 100644 --- a/targets/esp32s3.json +++ b/targets/esp32s3.json @@ -20,6 +20,7 @@ "linkerscript": "targets/esp32s3.ld", "extra-files": [ "src/device/esp/esp32s3.S", + "src/device/esp/esp32s3_no_c_archive.S", "targets/esp32s3-interrupts.S", "src/internal/task/task_stack_esp32.S" ], From 88f29116f65423171cbff60782ea044b2d90d3be Mon Sep 17 00:00:00 2001 From: Hector Chu Date: Sun, 19 Apr 2026 20:28:53 +0100 Subject: [PATCH 23/30] for consistency --- builder/build.go | 12 ++++++------ compileopts/config.go | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/builder/build.go b/builder/build.go index 64eabceec5..abd8780f5a 100644 --- a/builder/build.go +++ b/builder/build.go @@ -705,18 +705,18 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe result.Binary = result.Executable // final file ldflags := append(config.LDFlags(), "-o", result.Executable) - if config.Options.BuildMode == "c-archive" { + if config.BuildMode() == "c-archive" { ldflags = []string{"-r", "-o", result.Executable} } - if config.Options.BuildMode == "c-shared" { + if config.BuildMode() == "c-shared" { if !strings.HasPrefix(config.Triple(), "wasm32-") { return result, fmt.Errorf("buildmode c-shared is only supported on wasm at the moment") } ldflags = append(ldflags, "--no-entry") } - if config.Options.BuildMode == "wasi-legacy" { + if config.BuildMode() == "wasi-legacy" { if !strings.HasPrefix(config.Triple(), "wasm32-") { return result, fmt.Errorf("buildmode wasi-legacy is only supported on wasm") } @@ -728,7 +728,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe // Add compiler-rt dependency if needed. Usually this is a simple load from // a cache. - if config.Target.RTLib == "compiler-rt" && config.Options.BuildMode != "c-archive" { + if config.Target.RTLib == "compiler-rt" && config.BuildMode() != "c-archive" { job, unlock, err := libCompilerRT.load(config, tmpdir) if err != nil { return result, err @@ -789,7 +789,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe } // Add libc dependencies, if they exist. - if config.Options.BuildMode != "c-archive" { + if config.BuildMode() != "c-archive" { linkerDependencies = append(linkerDependencies, libcDependencies...) } @@ -871,7 +871,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe if err != nil { return err } - if config.Options.BuildMode == "c-archive" { + if config.BuildMode() == "c-archive" { result.Binary = result.Executable + ".a" f, err := os.Create(result.Binary) if err != nil { diff --git a/compileopts/config.go b/compileopts/config.go index 7a44e853c1..c315e158dc 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -474,7 +474,7 @@ func (c *Config) LDFlags() []string { // executable. This can include extra C and assembly files. func (c *Config) ExtraFiles() (paths []string) { for _, path := range c.Target.ExtraFiles { - if c.Options.BuildMode == "c-archive" { + if c.BuildMode() == "c-archive" { if strings.HasSuffix(path, "_no_c_archive"+filepath.Ext(path)) { continue } From cafc51514c68c363d06dc5f70c66e683d31b1e32 Mon Sep 17 00:00:00 2001 From: Hector Chu Date: Sun, 19 Apr 2026 21:08:03 +0100 Subject: [PATCH 24/30] add CGO_CFLAGS --- builder/build.go | 7 ++++--- goenv/goenv.go | 2 ++ loader/loader.go | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/builder/build.go b/builder/build.go index abd8780f5a..084807a673 100644 --- a/builder/build.go +++ b/builder/build.go @@ -147,6 +147,9 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe // the libc needs them. root := goenv.Get("TINYGOROOT") var libcDependencies []*compileJob + if config.BuildMode() == "c-archive" { + config.Target.Libc = "" + } switch config.Target.Libc { case "darwin-libSystem": libcJob := makeDarwinLibSystemJob(config, tmpdir) @@ -789,9 +792,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe } // Add libc dependencies, if they exist. - if config.BuildMode() != "c-archive" { - linkerDependencies = append(linkerDependencies, libcDependencies...) - } + linkerDependencies = append(linkerDependencies, libcDependencies...) // Add embedded files. linkerDependencies = append(linkerDependencies, embedFileObjects...) diff --git a/goenv/goenv.go b/goenv/goenv.go index fe4c8bf63e..cecb5a85dd 100644 --- a/goenv/goenv.go +++ b/goenv/goenv.go @@ -151,6 +151,8 @@ func Get(name string) string { panic("could not find cache dir: " + err.Error()) } return filepath.Join(dir, "tinygo") + case "CGO_CFLAGS": + return os.Getenv("CGO_CFLAGS") case "CGO_ENABLED": // Always enable CGo. It is required by a number of targets, including // macOS and the rp2040. diff --git a/loader/loader.go b/loader/loader.go index 1ca1b6679d..c323fb5193 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -485,6 +485,7 @@ func (p *Package) parseFiles() ([]*ast.File, error) { var initialCFlags []string initialCFlags = append(initialCFlags, p.program.config.CFlags(true)...) initialCFlags = append(initialCFlags, "-I"+p.Dir) + initialCFlags = append(initialCFlags, goenv.Get("CGO_CFLAGS")) generated, headerCode, cflags, ldflags, accessedFiles, errs := cgo.Process(files, p.program.workingDir, p.ImportPath, p.program.fset, initialCFlags, p.program.config.GOOS()) p.CFlags = append(initialCFlags, cflags...) p.CGoHeaders = headerCode From 85c4b7475fa9876ef8de5adf0f5c212f829cb9f0 Mon Sep 17 00:00:00 2001 From: Hector Chu Date: Sun, 19 Apr 2026 21:31:12 +0100 Subject: [PATCH 25/30] slightly tidier --- builder/build.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/builder/build.go b/builder/build.go index 084807a673..466df7ed3c 100644 --- a/builder/build.go +++ b/builder/build.go @@ -142,14 +142,16 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe } } + if config.BuildMode() == "c-archive" { + config.Target.Libc = "" + config.Target.RTLib = "" + } + // Check for a libc dependency. // As a side effect, this also creates the headers for the given libc, if // the libc needs them. root := goenv.Get("TINYGOROOT") var libcDependencies []*compileJob - if config.BuildMode() == "c-archive" { - config.Target.Libc = "" - } switch config.Target.Libc { case "darwin-libSystem": libcJob := makeDarwinLibSystemJob(config, tmpdir) @@ -731,7 +733,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe // Add compiler-rt dependency if needed. Usually this is a simple load from // a cache. - if config.Target.RTLib == "compiler-rt" && config.BuildMode() != "c-archive" { + if config.Target.RTLib == "compiler-rt" { job, unlock, err := libCompilerRT.load(config, tmpdir) if err != nil { return result, err From 604a482b1194a83c0b10721d81fb4526d43f3745 Mon Sep 17 00:00:00 2001 From: Hector Chu Date: Mon, 20 Apr 2026 09:46:44 +0100 Subject: [PATCH 26/30] simply rename the clashing symbols --- compileopts/config.go | 12 +- src/device/esp/esp32.S | 52 +++ src/device/esp/esp32_no_c_archive.S | 51 --- .../esp/{esp32c3_no_c_archive.S => esp32c3.S} | 14 +- src/device/esp/esp32s3.S | 345 ++++++++++++++++++ src/device/esp/esp32s3_no_c_archive.S | 344 ----------------- targets/esp32.json | 1 - targets/esp32.ld | 2 +- targets/esp32c3.json | 2 +- targets/esp32c3.ld | 2 +- targets/esp32s3.json | 1 - targets/esp32s3.ld | 2 +- .../gen-critical-atomics.go | 2 +- 13 files changed, 411 insertions(+), 419 deletions(-) delete mode 100644 src/device/esp/esp32_no_c_archive.S rename src/device/esp/{esp32c3_no_c_archive.S => esp32c3.S} (92%) delete mode 100644 src/device/esp/esp32s3_no_c_archive.S diff --git a/compileopts/config.go b/compileopts/config.go index c315e158dc..bb7935f065 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -472,16 +472,8 @@ func (c *Config) LDFlags() []string { // ExtraFiles returns the list of extra files to be built and linked with the // executable. This can include extra C and assembly files. -func (c *Config) ExtraFiles() (paths []string) { - for _, path := range c.Target.ExtraFiles { - if c.BuildMode() == "c-archive" { - if strings.HasSuffix(path, "_no_c_archive"+filepath.Ext(path)) { - continue - } - } - paths = append(paths, path) - } - return +func (c *Config) ExtraFiles() []string { + return c.Target.ExtraFiles } // DumpSSA returns whether to dump Go SSA while compiling (-dumpssa flag). Only diff --git a/src/device/esp/esp32.S b/src/device/esp/esp32.S index f4fe560bc7..6bd325079c 100644 --- a/src/device/esp/esp32.S +++ b/src/device/esp/esp32.S @@ -1,3 +1,55 @@ + +// The following definitions were copied from: +// esp-idf/components/xtensa/include/xtensa/corebits.h +#define PS_WOE_MASK 0x00040000 +#define PS_OWB_MASK 0x00000F00 +#define PS_CALLINC_MASK 0x00030000 +#define PS_WOE PS_WOE_MASK + +// Only calling it call_start_cpu0 for consistency with ESP-IDF. +.section .text.call_start_cpu0 +1: + .long _stack_top +.global _call_start_cpu0 +_call_start_cpu0: + // We need to set the stack pointer to a different value. This is somewhat + // complicated in the Xtensa architecture. The code below is a modified + // version of the following code: + // https://github.com/espressif/esp-idf/blob/c77c4ccf/components/xtensa/include/xt_instr_macros.h#L47 + + // Disable WOE. + rsr.ps a2 + movi a3, ~(PS_WOE_MASK) + and a2, a2, a3 + wsr.ps a2 + rsync + + // Set WINDOWSTART to 1 << WINDOWBASE. + rsr.windowbase a2 + ssl a2 + movi a2, 1 + sll a2, a2 + wsr.windowstart a2 + rsync + + // Load new stack pointer. + l32r sp, 1b + + // Re-enable WOE. + rsr.ps a2 + movi a3, PS_WOE + or a2, a2, a3 + wsr.ps a2 + rsync + + // Enable the FPU (coprocessor 0 so the lowest bit). + movi a2, 1 + wsr.cpenable a2 + rsync + + // Jump to the runtime start function written in Go. + call4 main + .section .text.tinygo_scanCurrentStack .global tinygo_scanCurrentStack tinygo_scanCurrentStack: diff --git a/src/device/esp/esp32_no_c_archive.S b/src/device/esp/esp32_no_c_archive.S deleted file mode 100644 index c8497779fc..0000000000 --- a/src/device/esp/esp32_no_c_archive.S +++ /dev/null @@ -1,51 +0,0 @@ - -// The following definitions were copied from: -// esp-idf/components/xtensa/include/xtensa/corebits.h -#define PS_WOE_MASK 0x00040000 -#define PS_OWB_MASK 0x00000F00 -#define PS_CALLINC_MASK 0x00030000 -#define PS_WOE PS_WOE_MASK - -// Only calling it call_start_cpu0 for consistency with ESP-IDF. -.section .text.call_start_cpu0 -1: - .long _stack_top -.global call_start_cpu0 -call_start_cpu0: - // We need to set the stack pointer to a different value. This is somewhat - // complicated in the Xtensa architecture. The code below is a modified - // version of the following code: - // https://github.com/espressif/esp-idf/blob/c77c4ccf/components/xtensa/include/xt_instr_macros.h#L47 - - // Disable WOE. - rsr.ps a2 - movi a3, ~(PS_WOE_MASK) - and a2, a2, a3 - wsr.ps a2 - rsync - - // Set WINDOWSTART to 1 << WINDOWBASE. - rsr.windowbase a2 - ssl a2 - movi a2, 1 - sll a2, a2 - wsr.windowstart a2 - rsync - - // Load new stack pointer. - l32r sp, 1b - - // Re-enable WOE. - rsr.ps a2 - movi a3, PS_WOE - or a2, a2, a3 - wsr.ps a2 - rsync - - // Enable the FPU (coprocessor 0 so the lowest bit). - movi a2, 1 - wsr.cpenable a2 - rsync - - // Jump to the runtime start function written in Go. - call4 main diff --git a/src/device/esp/esp32c3_no_c_archive.S b/src/device/esp/esp32c3.S similarity index 92% rename from src/device/esp/esp32c3_no_c_archive.S rename to src/device/esp/esp32c3.S index 0395d73bcf..ec9e6d72dd 100644 --- a/src/device/esp/esp32c3_no_c_archive.S +++ b/src/device/esp/esp32c3.S @@ -9,9 +9,9 @@ // https://www.imperialviolet.org/2016/12/31/riscv.html .section .init -.global call_start_cpu0 -.type call_start_cpu0,@function -call_start_cpu0: +.global _call_start_cpu0 +.type _call_start_cpu0,@function +_call_start_cpu0: // At this point: // - The ROM bootloader is finished and has jumped to here. // - We're running from IRAM: both IRAM and DRAM segments have been loaded @@ -49,10 +49,10 @@ call_start_cpu0: j _start .section .text.exception_vectors -.global _vector_table -.type _vector_table,@function +.global __vector_table +.type __vector_table,@function -_vector_table: +__vector_table: .option push .option norvc @@ -63,5 +63,5 @@ _vector_table: .option pop -.size _vector_table, .-_vector_table +.size __vector_table, .-__vector_table diff --git a/src/device/esp/esp32s3.S b/src/device/esp/esp32s3.S index 6d066836fa..99b538a42f 100644 --- a/src/device/esp/esp32s3.S +++ b/src/device/esp/esp32s3.S @@ -1,3 +1,348 @@ +// Startup code for the ESP32-S3 (Xtensa LX7, windowed ABI). +// +// The ROM bootloader loads IRAM/DRAM segments into SRAM but does NOT +// configure flash cache/MMU. We must: +// 1. Set up the windowed-ABI register file and stack pointer. +// 2. Set VECBASE and clear PS.EXCM (needed for callx4 window overflows). +// 3. Disable watchdog timers. +// 4. Configure the flash cache and MMU so IROM/DROM are accessible. +// 5. Jump to runtime.main (in IROM). +// +// Cache/MMU init sequence (from NuttX esp_loader.c / ESP-IDF bootloader / esp-hal): +// Phase A — configure cache modes: +// a. rom_config_instruction_cache_mode(16KB, 8-way, 32B) +// b. rom_Cache_Suspend_DCache() +// c. rom_config_data_cache_mode(32KB, 8-way, 32B) +// d. Cache_Resume_DCache(0) +// Phase B — map flash pages: +// e. Disable caches +// f. Cache_MMU_Init() — reset all MMU entries to invalid +// g. Cache_Set_IDROM_MMU_Size() — set IROM/DROM entry split +// h. Write MMU entries mapping flash page 0 for IROM and DROM +// i. Clear bus-shut bits +// j. Enable caches + isync + +#define PS_WOE 0x00040000 + +// ----------------------------------------------------------------------- +// Boot entry point — placed in IRAM by the linker. +// ----------------------------------------------------------------------- +.section .text.call_start_cpu0 + .literal_position + .align 4 +.Lstack_top_addr: + .long _stack_top +.Lmain_addr: + .long main +.Lvector_table_addr: + .long _vector_table +// WDT register addresses +.Lwdt_key: + .long 0x50D83AA1 +.Lrtc_wdt_protect: + .long 0x600080B0 +.Lrtc_wdt_config0: + .long 0x60008098 +.Ltimg0_wdt_protect: + .long 0x6001F064 +.Ltimg0_wdt_config0: + .long 0x6001F048 +.Ltimg1_wdt_protect: + .long 0x60020064 +.Ltimg1_wdt_config0: + .long 0x60020048 +.Lswd_protect: + .long 0x600080B8 +.Lswd_key: + .long 0x8F1D312A +.Lswd_conf: + .long 0x600080B4 +.Lswd_disable: + .long 0x40000000 +// ROM function addresses (from ESP-IDF esp32s3.rom.ld) +.Lrom_config_icache: + .long 0x40001a1c +.Lrom_config_dcache: + .long 0x40001a28 +.Lrom_suspend_dcache: + .long 0x400018b4 +.LCache_Resume_DCache: + .long 0x400018c0 +.LCache_Disable_ICache: + .long 0x4000186c +.LCache_Disable_DCache: + .long 0x40001884 +.LCache_MMU_Init: + .long 0x40001998 +.LCache_Set_IDROM_MMU_Size: + .long 0x40001914 +.LCache_Enable_ICache: + .long 0x40001878 +.LCache_Enable_DCache: + .long 0x40001890 +// Cache/MMU register addresses +.Lmmu_table_base: + .long 0x600C5000 +.Licache_ctrl1_reg: + .long 0x600C4064 +.Ldcache_ctrl1_reg: + .long 0x600C4004 +// End-of-section symbols for multi-page MMU mapping. +.Lirom_end: + .long _irom_end +.Ldrom_end: + .long _drom_end +.Lirom_base: + .long 0x42000000 +.Ldrom_base: + .long 0x3C000000 + +.global _call_start_cpu0 +_call_start_cpu0: + + // ---- 1. Windowed-ABI register file setup ---- + + // Disable WOE so we can safely manipulate WINDOWSTART. + rsr.ps a2 + movi a3, ~(PS_WOE) + and a2, a2, a3 + wsr.ps a2 + rsync + + // Set WINDOWSTART to 1 << WINDOWBASE (mark only current window as valid). + rsr.windowbase a2 + ssl a2 + movi a2, 1 + sll a2, a2 + wsr.windowstart a2 + rsync + + // Load stack pointer. + l32r a1, .Lstack_top_addr + + // Re-enable WOE. + rsr.ps a2 + movi a3, PS_WOE + or a2, a2, a3 + wsr.ps a2 + rsync + + // Enable FPU (coprocessor 0). + movi a2, 1 + wsr.cpenable a2 + rsync + + // ---- 2. Disable all watchdog timers (IMMEDIATELY, before any delay) ---- + l32r a3, .Lwdt_key + movi a4, 0 + + // RTC WDT + l32r a2, .Lrtc_wdt_protect + memw + s32i a3, a2, 0 + l32r a5, .Lrtc_wdt_config0 + memw + s32i a4, a5, 0 + memw + s32i a4, a2, 0 + + // TIMG0 WDT + l32r a2, .Ltimg0_wdt_protect + memw + s32i a3, a2, 0 + l32r a5, .Ltimg0_wdt_config0 + memw + s32i a4, a5, 0 + memw + s32i a4, a2, 0 + + // TIMG1 WDT + l32r a2, .Ltimg1_wdt_protect + memw + s32i a3, a2, 0 + l32r a5, .Ltimg1_wdt_config0 + memw + s32i a4, a5, 0 + memw + s32i a4, a2, 0 + + // Super WDT + l32r a2, .Lswd_protect + l32r a3, .Lswd_key + memw + s32i a3, a2, 0 + l32r a5, .Lswd_conf + l32r a6, .Lswd_disable + memw + s32i a6, a5, 0 + memw + s32i a4, a2, 0 + + // ---- 3. Set VECBASE and clear PS.EXCM ---- + // VECBASE must be set before any callx4 so that window overflow + // exceptions (triggered by register window rotation) route to our + // handlers in IRAM, not the ROM's default vectors. + l32r a8, .Lvector_table_addr + wsr.vecbase a8 + rsync + + // Clear PS.EXCM (bit 4) and PS.INTLEVEL (bits 0-3). + // The ROM bootloader may leave EXCM=1; with EXCM set any callx4 + // window overflow would become a double exception. + // Set PS.UM (bit 5) so level-1 exceptions route to User vector. + rsr.ps a2 + movi a3, ~0x1F + and a2, a2, a3 + movi a3, 0x20 + or a2, a2, a3 + wsr.ps a2 + rsync + + // ---- 4. Configure flash cache and MMU ---- + // + // ROM function calls use callx4 (windowed ABI): + // a4 = target address (overwritten with return addr by call mechanism) + // a5 = stack pointer for callee (becomes callee's a1 via entry) + // a6 = first argument (becomes callee's a2) + // a7 = second argument (becomes callee's a3) + // a8 = third argument (becomes callee's a4) + // Registers a0-a3 are preserved across callx4; a4-a11 may be clobbered. + + // Phase A: Configure cache modes (required for cache hardware to function). + // Without this, the cache doesn't know its size/associativity/line-size + // and cannot service flash accesses. + + // 4a. Configure ICache mode: 16KB, 8-way, 32-byte line + movi a6, 0x4000 // cache_size = 16KB + movi a7, 8 // ways = 8 + movi a8, 32 // line_size = 32 + mov a5, a1 + l32r a4, .Lrom_config_icache + callx4 a4 + + // 4b. Suspend DCache before configuring it + mov a5, a1 + l32r a4, .Lrom_suspend_dcache + callx4 a4 + + // 4c. Configure DCache mode: 32KB, 8-way, 32-byte line + movi a6, 0x8000 // cache_size = 32KB + movi a7, 8 // ways = 8 + movi a8, 32 // line_size = 32 + mov a5, a1 + l32r a4, .Lrom_config_dcache + callx4 a4 + + // 4d. Resume DCache + movi a6, 0 + mov a5, a1 + l32r a4, .LCache_Resume_DCache + callx4 a4 + + // Phase B: Map flash pages into MMU. + + // 4e. Disable ICache + mov a5, a1 + l32r a4, .LCache_Disable_ICache + callx4 a4 + + // 4f. Disable DCache + mov a5, a1 + l32r a4, .LCache_Disable_DCache + callx4 a4 + + // 4g. Initialize MMU (resets all 512 entries to invalid = 0x4000) + mov a5, a1 + l32r a4, .LCache_MMU_Init + callx4 a4 + + // 4h. Set IDROM MMU size: even 256/256 split. + // Each entry is 4 bytes, so 256 entries = 0x400 bytes per region. + movi a6, 0x400 // irom_mmu_size (256 entries × 4 bytes) + movi a7, 0x400 // drom_mmu_size (256 entries × 4 bytes) + mov a5, a1 + l32r a4, .LCache_Set_IDROM_MMU_Size + callx4 a4 + + // 4i. Map flash pages for IROM and DROM using identity mapping. + // MMU table at 0x600C5000: entries 0-255 = ICache, 256-511 = DCache. + // Entry value N = flash page N (SOC_MMU_VALID = 0 on S3). + // Each 64KB page needs one 4-byte entry. + // + // IROM: map pages 0..N where N = (_irom_end - 0x42000000) >> 16 + // DROM: map pages 0..M where M = (_drom_end - 0x3C000000) >> 16 + + l32r a8, .Lmmu_table_base // a8 = 0x600C5000 + + // --- IROM pages --- + l32r a2, .Lirom_end // a2 = _irom_end (VMA in 0x42xxxxxx) + l32r a3, .Lirom_base // a3 = 0x42000000 + sub a2, a2, a3 // a2 = byte offset past IROM base + srli a2, a2, 16 // a2 = last page index + addi a2, a2, 1 // a2 = number of pages to map + movi a9, 0 // a9 = page counter (and entry value) + mov a10, a8 // a10 = current MMU entry pointer +.Lirom_loop: + s32i a9, a10, 0 + addi a9, a9, 1 + addi a10, a10, 4 + blt a9, a2, .Lirom_loop + + // --- DROM pages --- + l32r a2, .Ldrom_end // a2 = _drom_end (VMA in 0x3Cxxxxxx) + l32r a3, .Ldrom_base // a3 = 0x3C000000 + sub a2, a2, a3 // a2 = byte offset past DROM base + srli a2, a2, 16 // a2 = last page index + addi a2, a2, 1 // a2 = number of pages to map + movi a9, 0 // a9 = page counter (and entry value) + addmi a10, a8, 0x400 // a10 = 0x600C5400 (DCache entry 256) +.Ldrom_loop: + s32i a9, a10, 0 + addi a9, a9, 1 + addi a10, a10, 4 + blt a9, a2, .Ldrom_loop + memw + + // 4j. Clear bus-shut bits so core 0 can access ICache and DCache buses. + l32r a8, .Licache_ctrl1_reg // 0x600C4064 + movi a9, 0 + s32i a9, a8, 0 // Clear all ICACHE_CTRL1 shut bits + l32r a8, .Ldcache_ctrl1_reg // 0x600C4004 + s32i a9, a8, 0 // Clear all DCACHE_CTRL1 shut bits + memw + + // 4k. Enable ICache (arg: autoload = 0) + movi a6, 0 + mov a5, a1 + l32r a4, .LCache_Enable_ICache + callx4 a4 + + // 4l. Enable DCache (arg: autoload = 0) + movi a6, 0 + mov a5, a1 + l32r a4, .LCache_Enable_DCache + callx4 a4 + + // Flush instruction pipeline so new cache/MMU config takes effect. + isync + + // ---- 5. Jump to main (in IROM) ---- + // Re-clear PS.EXCM in case ROM calls changed processor state. + rsr.ps a2 + movi a3, ~0x1F + and a2, a2, a3 + movi a3, 0x20 + or a2, a2, a3 + wsr.ps a2 + rsync + + mov a5, a1 + l32r a4, .Lmain_addr + callx4 a4 + + // If main returns, loop forever. +1: j 1b + // ----------------------------------------------------------------------- // tinygo_scanCurrentStack — Spill all Xtensa register windows to the // stack, then call tinygo_scanstack(sp) so the conservative GC can diff --git a/src/device/esp/esp32s3_no_c_archive.S b/src/device/esp/esp32s3_no_c_archive.S deleted file mode 100644 index 5572a3d484..0000000000 --- a/src/device/esp/esp32s3_no_c_archive.S +++ /dev/null @@ -1,344 +0,0 @@ -// Startup code for the ESP32-S3 (Xtensa LX7, windowed ABI). -// -// The ROM bootloader loads IRAM/DRAM segments into SRAM but does NOT -// configure flash cache/MMU. We must: -// 1. Set up the windowed-ABI register file and stack pointer. -// 2. Set VECBASE and clear PS.EXCM (needed for callx4 window overflows). -// 3. Disable watchdog timers. -// 4. Configure the flash cache and MMU so IROM/DROM are accessible. -// 5. Jump to runtime.main (in IROM). -// -// Cache/MMU init sequence (from NuttX esp_loader.c / ESP-IDF bootloader / esp-hal): -// Phase A — configure cache modes: -// a. rom_config_instruction_cache_mode(16KB, 8-way, 32B) -// b. rom_Cache_Suspend_DCache() -// c. rom_config_data_cache_mode(32KB, 8-way, 32B) -// d. Cache_Resume_DCache(0) -// Phase B — map flash pages: -// e. Disable caches -// f. Cache_MMU_Init() — reset all MMU entries to invalid -// g. Cache_Set_IDROM_MMU_Size() — set IROM/DROM entry split -// h. Write MMU entries mapping flash page 0 for IROM and DROM -// i. Clear bus-shut bits -// j. Enable caches + isync - -#define PS_WOE 0x00040000 - -// ----------------------------------------------------------------------- -// Boot entry point — placed in IRAM by the linker. -// ----------------------------------------------------------------------- -.section .text.call_start_cpu0 - .literal_position - .align 4 -.Lstack_top_addr: - .long _stack_top -.Lmain_addr: - .long main -.Lvector_table_addr: - .long _vector_table -// WDT register addresses -.Lwdt_key: - .long 0x50D83AA1 -.Lrtc_wdt_protect: - .long 0x600080B0 -.Lrtc_wdt_config0: - .long 0x60008098 -.Ltimg0_wdt_protect: - .long 0x6001F064 -.Ltimg0_wdt_config0: - .long 0x6001F048 -.Ltimg1_wdt_protect: - .long 0x60020064 -.Ltimg1_wdt_config0: - .long 0x60020048 -.Lswd_protect: - .long 0x600080B8 -.Lswd_key: - .long 0x8F1D312A -.Lswd_conf: - .long 0x600080B4 -.Lswd_disable: - .long 0x40000000 -// ROM function addresses (from ESP-IDF esp32s3.rom.ld) -.Lrom_config_icache: - .long 0x40001a1c -.Lrom_config_dcache: - .long 0x40001a28 -.Lrom_suspend_dcache: - .long 0x400018b4 -.LCache_Resume_DCache: - .long 0x400018c0 -.LCache_Disable_ICache: - .long 0x4000186c -.LCache_Disable_DCache: - .long 0x40001884 -.LCache_MMU_Init: - .long 0x40001998 -.LCache_Set_IDROM_MMU_Size: - .long 0x40001914 -.LCache_Enable_ICache: - .long 0x40001878 -.LCache_Enable_DCache: - .long 0x40001890 -// Cache/MMU register addresses -.Lmmu_table_base: - .long 0x600C5000 -.Licache_ctrl1_reg: - .long 0x600C4064 -.Ldcache_ctrl1_reg: - .long 0x600C4004 -// End-of-section symbols for multi-page MMU mapping. -.Lirom_end: - .long _irom_end -.Ldrom_end: - .long _drom_end -.Lirom_base: - .long 0x42000000 -.Ldrom_base: - .long 0x3C000000 - -.global call_start_cpu0 -call_start_cpu0: - - // ---- 1. Windowed-ABI register file setup ---- - - // Disable WOE so we can safely manipulate WINDOWSTART. - rsr.ps a2 - movi a3, ~(PS_WOE) - and a2, a2, a3 - wsr.ps a2 - rsync - - // Set WINDOWSTART to 1 << WINDOWBASE (mark only current window as valid). - rsr.windowbase a2 - ssl a2 - movi a2, 1 - sll a2, a2 - wsr.windowstart a2 - rsync - - // Load stack pointer. - l32r a1, .Lstack_top_addr - - // Re-enable WOE. - rsr.ps a2 - movi a3, PS_WOE - or a2, a2, a3 - wsr.ps a2 - rsync - - // Enable FPU (coprocessor 0). - movi a2, 1 - wsr.cpenable a2 - rsync - - // ---- 2. Disable all watchdog timers (IMMEDIATELY, before any delay) ---- - l32r a3, .Lwdt_key - movi a4, 0 - - // RTC WDT - l32r a2, .Lrtc_wdt_protect - memw - s32i a3, a2, 0 - l32r a5, .Lrtc_wdt_config0 - memw - s32i a4, a5, 0 - memw - s32i a4, a2, 0 - - // TIMG0 WDT - l32r a2, .Ltimg0_wdt_protect - memw - s32i a3, a2, 0 - l32r a5, .Ltimg0_wdt_config0 - memw - s32i a4, a5, 0 - memw - s32i a4, a2, 0 - - // TIMG1 WDT - l32r a2, .Ltimg1_wdt_protect - memw - s32i a3, a2, 0 - l32r a5, .Ltimg1_wdt_config0 - memw - s32i a4, a5, 0 - memw - s32i a4, a2, 0 - - // Super WDT - l32r a2, .Lswd_protect - l32r a3, .Lswd_key - memw - s32i a3, a2, 0 - l32r a5, .Lswd_conf - l32r a6, .Lswd_disable - memw - s32i a6, a5, 0 - memw - s32i a4, a2, 0 - - // ---- 3. Set VECBASE and clear PS.EXCM ---- - // VECBASE must be set before any callx4 so that window overflow - // exceptions (triggered by register window rotation) route to our - // handlers in IRAM, not the ROM's default vectors. - l32r a8, .Lvector_table_addr - wsr.vecbase a8 - rsync - - // Clear PS.EXCM (bit 4) and PS.INTLEVEL (bits 0-3). - // The ROM bootloader may leave EXCM=1; with EXCM set any callx4 - // window overflow would become a double exception. - // Set PS.UM (bit 5) so level-1 exceptions route to User vector. - rsr.ps a2 - movi a3, ~0x1F - and a2, a2, a3 - movi a3, 0x20 - or a2, a2, a3 - wsr.ps a2 - rsync - - // ---- 4. Configure flash cache and MMU ---- - // - // ROM function calls use callx4 (windowed ABI): - // a4 = target address (overwritten with return addr by call mechanism) - // a5 = stack pointer for callee (becomes callee's a1 via entry) - // a6 = first argument (becomes callee's a2) - // a7 = second argument (becomes callee's a3) - // a8 = third argument (becomes callee's a4) - // Registers a0-a3 are preserved across callx4; a4-a11 may be clobbered. - - // Phase A: Configure cache modes (required for cache hardware to function). - // Without this, the cache doesn't know its size/associativity/line-size - // and cannot service flash accesses. - - // 4a. Configure ICache mode: 16KB, 8-way, 32-byte line - movi a6, 0x4000 // cache_size = 16KB - movi a7, 8 // ways = 8 - movi a8, 32 // line_size = 32 - mov a5, a1 - l32r a4, .Lrom_config_icache - callx4 a4 - - // 4b. Suspend DCache before configuring it - mov a5, a1 - l32r a4, .Lrom_suspend_dcache - callx4 a4 - - // 4c. Configure DCache mode: 32KB, 8-way, 32-byte line - movi a6, 0x8000 // cache_size = 32KB - movi a7, 8 // ways = 8 - movi a8, 32 // line_size = 32 - mov a5, a1 - l32r a4, .Lrom_config_dcache - callx4 a4 - - // 4d. Resume DCache - movi a6, 0 - mov a5, a1 - l32r a4, .LCache_Resume_DCache - callx4 a4 - - // Phase B: Map flash pages into MMU. - - // 4e. Disable ICache - mov a5, a1 - l32r a4, .LCache_Disable_ICache - callx4 a4 - - // 4f. Disable DCache - mov a5, a1 - l32r a4, .LCache_Disable_DCache - callx4 a4 - - // 4g. Initialize MMU (resets all 512 entries to invalid = 0x4000) - mov a5, a1 - l32r a4, .LCache_MMU_Init - callx4 a4 - - // 4h. Set IDROM MMU size: even 256/256 split. - // Each entry is 4 bytes, so 256 entries = 0x400 bytes per region. - movi a6, 0x400 // irom_mmu_size (256 entries × 4 bytes) - movi a7, 0x400 // drom_mmu_size (256 entries × 4 bytes) - mov a5, a1 - l32r a4, .LCache_Set_IDROM_MMU_Size - callx4 a4 - - // 4i. Map flash pages for IROM and DROM using identity mapping. - // MMU table at 0x600C5000: entries 0-255 = ICache, 256-511 = DCache. - // Entry value N = flash page N (SOC_MMU_VALID = 0 on S3). - // Each 64KB page needs one 4-byte entry. - // - // IROM: map pages 0..N where N = (_irom_end - 0x42000000) >> 16 - // DROM: map pages 0..M where M = (_drom_end - 0x3C000000) >> 16 - - l32r a8, .Lmmu_table_base // a8 = 0x600C5000 - - // --- IROM pages --- - l32r a2, .Lirom_end // a2 = _irom_end (VMA in 0x42xxxxxx) - l32r a3, .Lirom_base // a3 = 0x42000000 - sub a2, a2, a3 // a2 = byte offset past IROM base - srli a2, a2, 16 // a2 = last page index - addi a2, a2, 1 // a2 = number of pages to map - movi a9, 0 // a9 = page counter (and entry value) - mov a10, a8 // a10 = current MMU entry pointer -.Lirom_loop: - s32i a9, a10, 0 - addi a9, a9, 1 - addi a10, a10, 4 - blt a9, a2, .Lirom_loop - - // --- DROM pages --- - l32r a2, .Ldrom_end // a2 = _drom_end (VMA in 0x3Cxxxxxx) - l32r a3, .Ldrom_base // a3 = 0x3C000000 - sub a2, a2, a3 // a2 = byte offset past DROM base - srli a2, a2, 16 // a2 = last page index - addi a2, a2, 1 // a2 = number of pages to map - movi a9, 0 // a9 = page counter (and entry value) - addmi a10, a8, 0x400 // a10 = 0x600C5400 (DCache entry 256) -.Ldrom_loop: - s32i a9, a10, 0 - addi a9, a9, 1 - addi a10, a10, 4 - blt a9, a2, .Ldrom_loop - memw - - // 4j. Clear bus-shut bits so core 0 can access ICache and DCache buses. - l32r a8, .Licache_ctrl1_reg // 0x600C4064 - movi a9, 0 - s32i a9, a8, 0 // Clear all ICACHE_CTRL1 shut bits - l32r a8, .Ldcache_ctrl1_reg // 0x600C4004 - s32i a9, a8, 0 // Clear all DCACHE_CTRL1 shut bits - memw - - // 4k. Enable ICache (arg: autoload = 0) - movi a6, 0 - mov a5, a1 - l32r a4, .LCache_Enable_ICache - callx4 a4 - - // 4l. Enable DCache (arg: autoload = 0) - movi a6, 0 - mov a5, a1 - l32r a4, .LCache_Enable_DCache - callx4 a4 - - // Flush instruction pipeline so new cache/MMU config takes effect. - isync - - // ---- 5. Jump to main (in IROM) ---- - // Re-clear PS.EXCM in case ROM calls changed processor state. - rsr.ps a2 - movi a3, ~0x1F - and a2, a2, a3 - movi a3, 0x20 - or a2, a2, a3 - wsr.ps a2 - rsync - - mov a5, a1 - l32r a4, .Lmain_addr - callx4 a4 - - // If main returns, loop forever. -1: j 1b diff --git a/targets/esp32.json b/targets/esp32.json index c87568cd0e..2c7abd6993 100644 --- a/targets/esp32.json +++ b/targets/esp32.json @@ -13,7 +13,6 @@ "linkerscript": "targets/esp32.ld", "extra-files": [ "src/device/esp/esp32.S", - "src/device/esp/esp32_no_c_archive.S", "src/internal/task/task_stack_esp32.S" ], "binary-format": "esp32", diff --git a/targets/esp32.ld b/targets/esp32.ld index 6818ce3190..7dab4a3024 100644 --- a/targets/esp32.ld +++ b/targets/esp32.ld @@ -19,7 +19,7 @@ MEMORY /* The entry point. It is set in the image flashed to the chip, so must be * defined. */ -ENTRY(call_start_cpu0) +ENTRY(_call_start_cpu0) SECTIONS { diff --git a/targets/esp32c3.json b/targets/esp32c3.json index 0e8817c9ca..583506c19f 100644 --- a/targets/esp32c3.json +++ b/targets/esp32c3.json @@ -12,7 +12,7 @@ ], "linkerscript": "targets/esp32c3.ld", "extra-files": [ - "src/device/esp/esp32c3_no_c_archive.S" + "src/device/esp/esp32c3.S" ], "binary-format": "esp32c3", "flash-method": "esp32jtag", diff --git a/targets/esp32c3.ld b/targets/esp32c3.ld index 5fe8cc89d2..ec104abf4b 100644 --- a/targets/esp32c3.ld +++ b/targets/esp32c3.ld @@ -44,7 +44,7 @@ MEMORY /* The entry point. It is set in the image flashed to the chip, so must be * defined. */ -ENTRY(call_start_cpu0) +ENTRY(_call_start_cpu0) SECTIONS { diff --git a/targets/esp32s3.json b/targets/esp32s3.json index 109664d0e0..ea6527d6d5 100644 --- a/targets/esp32s3.json +++ b/targets/esp32s3.json @@ -20,7 +20,6 @@ "linkerscript": "targets/esp32s3.ld", "extra-files": [ "src/device/esp/esp32s3.S", - "src/device/esp/esp32s3_no_c_archive.S", "targets/esp32s3-interrupts.S", "src/internal/task/task_stack_esp32.S" ], diff --git a/targets/esp32s3.ld b/targets/esp32s3.ld index 6a08f29983..d90e81b1ba 100644 --- a/targets/esp32s3.ld +++ b/targets/esp32s3.ld @@ -25,7 +25,7 @@ MEMORY /* The entry point. It is set in the image flashed to the chip, so must be * defined. */ -ENTRY(call_start_cpu0) +ENTRY(_call_start_cpu0) SECTIONS { diff --git a/tools/gen-critical-atomics/gen-critical-atomics.go b/tools/gen-critical-atomics/gen-critical-atomics.go index 98ceebb020..3bdf2eda8f 100644 --- a/tools/gen-critical-atomics/gen-critical-atomics.go +++ b/tools/gen-critical-atomics/gen-critical-atomics.go @@ -17,7 +17,7 @@ var tmpl = template.Must(template.New("go").Funcs(template.FuncMap{ return v }, "title": strings.Title, -}).Parse(`//go:build baremetal && !tinygo.wasm +}).Parse(`//go:build baremetal && !tinygo.wasm && !buildmode.c_archive // Automatically generated file. DO NOT EDIT. // This file implements standins for non-native atomics using critical sections. From 0bb44a8ca65f1bb67c73eec5e05f163d0cd19937 Mon Sep 17 00:00:00 2001 From: Hector Chu Date: Mon, 20 Apr 2026 10:23:02 +0100 Subject: [PATCH 27/30] interrupt handling done by OS --- src/runtime/interrupt/interrupt_none.go | 2 +- src/runtime/interrupt/interrupt_tinygoriscv.go | 2 +- src/runtime/runtime_esp32c3.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/runtime/interrupt/interrupt_none.go b/src/runtime/interrupt/interrupt_none.go index ea8bdb68c6..9e27a6d565 100644 --- a/src/runtime/interrupt/interrupt_none.go +++ b/src/runtime/interrupt/interrupt_none.go @@ -1,4 +1,4 @@ -//go:build !baremetal || tkey +//go:build !baremetal || tkey || buildmode.c_archive package interrupt diff --git a/src/runtime/interrupt/interrupt_tinygoriscv.go b/src/runtime/interrupt/interrupt_tinygoriscv.go index 558e67150c..8c61d42b13 100644 --- a/src/runtime/interrupt/interrupt_tinygoriscv.go +++ b/src/runtime/interrupt/interrupt_tinygoriscv.go @@ -1,4 +1,4 @@ -//go:build tinygo.riscv && !tkey +//go:build tinygo.riscv && !tkey && !buildmode.c_archive package interrupt diff --git a/src/runtime/runtime_esp32c3.go b/src/runtime/runtime_esp32c3.go index 761a14d1cd..36d1189204 100644 --- a/src/runtime/runtime_esp32c3.go +++ b/src/runtime/runtime_esp32c3.go @@ -173,5 +173,5 @@ func sleepTicks(d timeUnit) { } } -//go:extern _vector_table +//go:extern __vector_table var _vector_table [0]uintptr From 6d0d99f8c3b93ac174c8fae177cc474a941f31f4 Mon Sep 17 00:00:00 2001 From: Hector Chu Date: Mon, 20 Apr 2026 11:03:28 +0100 Subject: [PATCH 28/30] restrict to esp32c3 --- src/runtime/interrupt/interrupt_none.go | 2 +- src/runtime/interrupt/interrupt_tinygoriscv.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/runtime/interrupt/interrupt_none.go b/src/runtime/interrupt/interrupt_none.go index 9e27a6d565..97462d6f81 100644 --- a/src/runtime/interrupt/interrupt_none.go +++ b/src/runtime/interrupt/interrupt_none.go @@ -1,4 +1,4 @@ -//go:build !baremetal || tkey || buildmode.c_archive +//go:build !baremetal || tkey || (esp32c3 && buildmode.c_archive) package interrupt diff --git a/src/runtime/interrupt/interrupt_tinygoriscv.go b/src/runtime/interrupt/interrupt_tinygoriscv.go index 8c61d42b13..69caa13e7a 100644 --- a/src/runtime/interrupt/interrupt_tinygoriscv.go +++ b/src/runtime/interrupt/interrupt_tinygoriscv.go @@ -1,4 +1,4 @@ -//go:build tinygo.riscv && !tkey && !buildmode.c_archive +//go:build tinygo.riscv && !tkey && !(esp32c3 && buildmode.c_archive) package interrupt From b4a7075b4f07fffe2d7b7830bf4033c0b0c7570f Mon Sep 17 00:00:00 2001 From: Hector Chu Date: Mon, 20 Apr 2026 11:23:33 +0100 Subject: [PATCH 29/30] tinygo.riscv not esp32c3 --- src/runtime/interrupt/interrupt_none.go | 2 +- src/runtime/interrupt/interrupt_tinygoriscv.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/runtime/interrupt/interrupt_none.go b/src/runtime/interrupt/interrupt_none.go index 97462d6f81..d1f62c1ec3 100644 --- a/src/runtime/interrupt/interrupt_none.go +++ b/src/runtime/interrupt/interrupt_none.go @@ -1,4 +1,4 @@ -//go:build !baremetal || tkey || (esp32c3 && buildmode.c_archive) +//go:build !baremetal || tkey || (tinygo.riscv && buildmode.c_archive) package interrupt diff --git a/src/runtime/interrupt/interrupt_tinygoriscv.go b/src/runtime/interrupt/interrupt_tinygoriscv.go index 69caa13e7a..8c61d42b13 100644 --- a/src/runtime/interrupt/interrupt_tinygoriscv.go +++ b/src/runtime/interrupt/interrupt_tinygoriscv.go @@ -1,4 +1,4 @@ -//go:build tinygo.riscv && !tkey && !(esp32c3 && buildmode.c_archive) +//go:build tinygo.riscv && !tkey && !buildmode.c_archive package interrupt From 823e602f4bcda8e8532677851472de95995975f4 Mon Sep 17 00:00:00 2001 From: Hector Chu Date: Mon, 20 Apr 2026 12:01:56 +0100 Subject: [PATCH 30/30] place tinygo_scanCurrentStack in same section as tinygo_scanstack to avoid dangerous relocation error (branch target out of range). --- src/device/esp/esp32.S | 2 +- src/device/esp/esp32s3.S | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/device/esp/esp32.S b/src/device/esp/esp32.S index 6bd325079c..8ea996a9d5 100644 --- a/src/device/esp/esp32.S +++ b/src/device/esp/esp32.S @@ -50,7 +50,7 @@ _call_start_cpu0: // Jump to the runtime start function written in Go. call4 main -.section .text.tinygo_scanCurrentStack +.section .text.tinygo_scanstack .global tinygo_scanCurrentStack tinygo_scanCurrentStack: // TODO: save callee saved registers on the stack diff --git a/src/device/esp/esp32s3.S b/src/device/esp/esp32s3.S index 99b538a42f..2c4f46dc38 100644 --- a/src/device/esp/esp32s3.S +++ b/src/device/esp/esp32s3.S @@ -355,7 +355,7 @@ _call_start_cpu0: // pointed to by the pane's a1 (sp). After all panes are flushed, a // scan from the current sp to stackTop covers every live value. // ----------------------------------------------------------------------- -.section .text.tinygo_scanCurrentStack +.section .text.tinygo_scanstack .global tinygo_scanCurrentStack tinygo_scanCurrentStack: