Merge pull request #1246 from MeteorsLiu/esp-libc

feat: support libc/compiler-rt for small places
This commit is contained in:
xushiwei
2025-09-03 07:05:51 +08:00
committed by GitHub
35 changed files with 4937 additions and 2482 deletions

View File

@@ -0,0 +1,16 @@
package main
import "github.com/goplus/lib/c"
func myprint(s *c.Char) {
for i := 0; i < int(c.Strlen(s)); i++ {
WriteByte(byte(c.Index(s, i)))
}
}
func main() {
for {
myprint(c.Str("hello world"))
sleep(1)
}
}

View File

@@ -0,0 +1,13 @@
package main
import (
_ "unsafe"
"github.com/goplus/lib/c"
)
//go:linkname WriteByte C.board_uart_write_char
func WriteByte(b byte)
//go:linkname sleep sleep
func sleep(c c.Int)

View File

@@ -0,0 +1,31 @@
package main
import (
_ "unsafe"
"github.com/goplus/lib/c"
)
//go:linkname write C.write
func write(c.Int, *c.Char, c.SizeT) int
func main() {
buf := c.Malloc(6)
c.Memset(buf, 0, 6)
c.Strncpy((*c.Char)(buf), c.Str("abcde"), 5)
if c.Strcmp((*c.Char)(buf), c.Str("abcde")) == 0 {
write(1, c.Str("pass strcmp"), 11)
}
if byte(c.Index((*c.Char)(buf), 0)) == 'a' {
write(1, c.Str("pass index"), 10)
}
c.Memset(buf, c.Int('A'), 5)
if c.Strcmp((*c.Char)(buf), c.Str("AAAAA")) == 0 {
write(1, c.Str("pass memeset"), 11)
}
write(1, (*c.Char)(buf), 5)
}

View File

@@ -839,15 +839,16 @@ func isWasmTarget(goos string) bool {
return slices.Contains([]string{"wasi", "js", "wasip1"}, goos)
}
func needStart(conf *Config) bool {
if conf.Target == "" {
return !isWasmTarget(conf.Goos)
func needStart(ctx *context) bool {
if ctx.buildConf.Target == "" {
return !isWasmTarget(ctx.buildConf.Goos)
}
switch conf.Target {
switch ctx.buildConf.Target {
case "wasip2":
return false
default:
return true
// since newlib-esp32 provides _start, we don't need to provide a fake _start function
return ctx.crossCompile.Libc != "newlib-esp32"
}
}
@@ -904,10 +905,10 @@ define weak void @_start() {
}
`
mainDefine := "define i32 @main(i32 noundef %0, ptr nocapture noundef readnone %1) local_unnamed_addr"
if !needStart(ctx.buildConf) && isWasmTarget(ctx.buildConf.Goos) {
if !needStart(ctx) && isWasmTarget(ctx.buildConf.Goos) {
mainDefine = "define hidden noundef i32 @__main_argc_argv(i32 noundef %0, ptr nocapture noundef readnone %1) local_unnamed_addr"
}
if !needStart(ctx.buildConf) {
if !needStart(ctx) {
startDefine = ""
}
mainCode := fmt.Sprintf(`; ModuleID = 'main'

View File

@@ -0,0 +1,103 @@
package compile
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"slices"
"strings"
"github.com/goplus/llgo/internal/clang"
)
type CompileOptions struct {
CC string // Compiler to use
Linker string
CCFLAGS []string
CFLAGS []string
LDFLAGS []string
}
type CompileGroup struct {
OutputFileName string
Files []string // List of source files to compile
CFlags []string // C compiler flags
CCFlags []string
LDFlags []string // Linker flags
}
func (g CompileGroup) IsCompiled(outputDir string) bool {
archive := filepath.Join(outputDir, filepath.Base(g.OutputFileName))
_, err := os.Stat(archive)
return err == nil
}
func (g CompileGroup) Compile(
outputDir string, options CompileOptions,
) (err error) {
if g.IsCompiled(outputDir) {
return
}
tmpCompileDir, err := os.MkdirTemp("", "compile-group*")
if err != nil {
return
}
defer os.RemoveAll(tmpCompileDir)
compileLDFlags := append(slices.Clone(options.LDFLAGS), g.LDFlags...)
compileCCFlags := append(slices.Clone(options.CCFLAGS), g.CCFlags...)
compileCFFlags := append(slices.Clone(options.CFLAGS), g.CFlags...)
cfg := clang.NewConfig(options.CC, compileCCFlags, compileCFFlags, compileLDFlags, options.Linker)
var objFiles []string
compiler := clang.NewCompiler(cfg)
compiler.Verbose = true
archive := filepath.Join(outputDir, filepath.Base(g.OutputFileName))
fmt.Fprintf(os.Stderr, "Start to compile group %s to %s...\n", g.OutputFileName, archive)
for _, file := range g.Files {
var tempObjFile *os.File
tempObjFile, err = os.CreateTemp(tmpCompileDir, fmt.Sprintf("%s*.o", strings.ReplaceAll(file, string(os.PathSeparator), "-")))
if err != nil {
return
}
lang := "c"
if filepath.Ext(file) == ".S" {
lang = "assembler-with-cpp"
}
err = compiler.Compile("-o", tempObjFile.Name(), "-x", lang, "-c", file)
if err != nil {
return
}
objFiles = append(objFiles, tempObjFile.Name())
}
args := []string{"rcs", archive}
args = append(args, objFiles...)
ccDir := filepath.Dir(options.CC)
llvmAr := filepath.Join(ccDir, "llvm-ar")
cmd := exec.Command(llvmAr, args...)
// TODO(MeteorsLiu): support verbose
// cmd.Stdout = os.Stdout
// cmd.Stderr = os.Stderr
err = cmd.Run()
return
}
// CompileConfig represents compilation configuration
type CompileConfig struct {
Url string
Name string // compile name (e.g., "picolibc", "musl", "glibc")
Groups []CompileGroup
ArchiveSrcDir string
LibcCFlags []string
}

View File

@@ -0,0 +1,209 @@
//go:build !llgo
package compile
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/goplus/llgo/xtool/nm"
)
func TestIsCompile(t *testing.T) {
t.Run("IsCompile Not Exists", func(t *testing.T) {
cfg := CompileConfig{
Groups: []CompileGroup{
{
OutputFileName: "fakefile1.a",
},
},
}
if cfg.Groups[0].IsCompiled(".") {
t.Errorf("unexpected result: should false")
}
})
t.Run("IsCompile Exists", func(t *testing.T) {
tmpFile, err := os.CreateTemp("", "test*.a")
if err != nil {
t.Error(err)
return
}
defer os.Remove(tmpFile.Name())
cfg := CompileConfig{
Groups: []CompileGroup{
{
OutputFileName: tmpFile.Name(),
},
},
}
if !cfg.Groups[0].IsCompiled(filepath.Dir(tmpFile.Name())) {
t.Errorf("unexpected result: should true")
}
})
}
func TestCompile(t *testing.T) {
t.Run("Skip compile", func(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "test-compile*")
if err != nil {
t.Error(err)
return
}
defer os.RemoveAll(tmpDir)
tmpFile, err := os.CreateTemp(tmpDir, "test*.a")
if err != nil {
t.Error(err)
return
}
group := CompileGroup{
OutputFileName: tmpFile.Name(),
}
err = group.Compile(tmpDir, CompileOptions{
CC: "clang",
Linker: "lld",
})
if err != nil {
t.Errorf("unexpected result: should nil: %v", err)
}
})
t.Run("TmpDir Fail", func(t *testing.T) {
tmpDir := filepath.Join(t.TempDir(), "test-compile")
os.RemoveAll(tmpDir)
err := os.Mkdir(tmpDir, 0)
if err != nil {
t.Error(err)
return
}
defer os.RemoveAll(tmpDir)
os.Setenv("TMPDIR", tmpDir)
defer os.Unsetenv("TMPDIR")
group := CompileGroup{
OutputFileName: "nop.a",
}
err = group.Compile(tmpDir, CompileOptions{
CC: "clang",
Linker: "lld",
})
if err == nil {
t.Errorf("unexpected result: should not nil")
}
})
t.Run("Compile", func(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "test-compile*")
if err != nil {
t.Error(err)
return
}
defer os.RemoveAll(tmpDir)
tmpFile, err := os.CreateTemp(tmpDir, "test*.c")
if err != nil {
t.Error(err)
return
}
_, err = tmpFile.Write([]byte(`#include <math.h>
void Foo() {
double x = 2.0;
double y = sqrt(x);
(void) y ;
}
`))
if err != nil {
t.Error(err)
return
}
group := CompileGroup{
OutputFileName: "nop.a",
Files: []string{tmpFile.Name()},
}
err = group.Compile(tmpDir, CompileOptions{
CC: "clang",
Linker: "lld",
CCFLAGS: []string{"-nostdinc"},
})
if err == nil {
t.Errorf("unexpected result: should not nil")
}
err = group.Compile(tmpDir, CompileOptions{
CC: "clang",
Linker: "lld",
})
if err != nil {
t.Errorf("unexpected result: should not nil")
}
if _, err := os.Stat(filepath.Join(tmpDir, "nop.a")); os.IsNotExist(err) {
t.Error("unexpected result: compiled nop.a not found")
return
}
items, err := nm.New("").List(filepath.Join(tmpDir, "nop.a"))
if err != nil {
t.Error(err)
return
}
want := "Foo"
found := false
loop:
for _, item := range items {
for _, sym := range item.Symbols {
if strings.Contains(sym.Name, want) {
found = true
break loop
}
}
}
if !found {
t.Errorf("cannot find symbol Foo")
}
})
t.Run("Compile Asm", func(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "test-compile*")
if err != nil {
t.Error(err)
return
}
defer os.RemoveAll(tmpDir)
tmpFile, err := os.CreateTemp(tmpDir, "test*.S")
if err != nil {
t.Error(err)
return
}
defer os.Remove(tmpFile.Name())
_, err = tmpFile.Write([]byte(`
.text
.globl _test
`))
if err != nil {
t.Error(err)
return
}
group := CompileGroup{
OutputFileName: "nop.a",
Files: []string{tmpFile.Name()},
}
err = group.Compile(tmpDir, CompileOptions{
CC: "clang",
Linker: "lld",
CCFLAGS: []string{"--target=x86_64-linux-gnu"},
})
if err != nil {
t.Errorf("unexpected result: should nil %v", err)
}
})
}

View File

@@ -0,0 +1,630 @@
package libc
import (
"path/filepath"
"testing"
)
func TestGetPicolibcConfig(t *testing.T) {
baseDir := "/test/base"
target := "test-target"
config := GetPicolibcConfig(baseDir, target)
if config.Name != "picolibc" {
t.Errorf("Expected Name 'picolibc', got '%s'", config.Name)
}
if config.ArchiveSrcDir != "picolibc-main" {
t.Errorf("Expected ArchiveSrcDir 'picolibc-main', got '%s'", config.ArchiveSrcDir)
}
// Test LibcCFlags
if len(config.LibcCFlags) != 2 {
t.Errorf("Expected 2 LibcCFlags, got %d", len(config.LibcCFlags))
} else {
expected := "-I" + baseDir
if config.LibcCFlags[0] != expected {
t.Errorf("Expected LibcCFlags[0] to be '%s', got '%s'", expected, config.LibcCFlags[0])
}
expected = "-isystem" + filepath.Join(baseDir, "newlib", "libc", "include")
if config.LibcCFlags[1] != expected {
t.Errorf("Expected LibcCFlags[1] to be '%s', got '%s'", expected, config.LibcCFlags[1])
}
}
// Test Groups configuration
if len(config.Groups) != 1 {
t.Errorf("Expected 1 group, got %d", len(config.Groups))
} else {
group := config.Groups[0]
// Test output file name
expectedOutput := "libc-" + target + ".a"
if group.OutputFileName != expectedOutput {
t.Errorf("Expected OutputFileName '%s', got '%s'", expectedOutput, group.OutputFileName)
}
// Test files list
if len(group.Files) == 0 {
t.Error("Expected non-empty files list")
} else {
// Check a few sample files
sampleFiles := []string{
filepath.Join(baseDir, "newlib", "libc", "string", "bcmp.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "memcpy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strlen.c"),
filepath.Join(baseDir, "newlib", "libc", "stdlib", "nano-malloc.c"),
filepath.Join(baseDir, "newlib", "libc", "tinystdio", "printf.c"),
}
for _, sample := range sampleFiles {
found := false
for _, file := range group.Files {
if file == sample {
found = true
break
}
}
if !found {
t.Errorf("Expected file '%s' not found in group files", sample)
}
}
}
// Test CFlags
expectedCFlags := []string{
"-D_COMPILING_NEWLIB",
"-D_HAVE_ALIAS_ATTRIBUTE",
"-DTINY_STDIO",
"-DPOSIX_IO",
"-DFORMAT_DEFAULT_INTEGER",
"-D_IEEE_LIBM",
"-D__OBSOLETE_MATH_FLOAT=1",
"-D__OBSOLETE_MATH_DOUBLE=0",
"-D_WANT_IO_C99_FORMATS",
"-nostdlib",
"-I" + baseDir,
"-isystem" + filepath.Join(baseDir, "newlib", "libc", "include"),
"-I" + filepath.Join(baseDir, "newlib", "libm", "common"),
"-I" + filepath.Join(baseDir, "newlib", "libc", "locale"),
"-I" + filepath.Join(baseDir, "newlib", "libc", "tinystdio"),
}
if len(group.CFlags) != len(expectedCFlags) {
t.Errorf("Expected %d CFlags, got %d", len(expectedCFlags), len(group.CFlags))
} else {
for i, expected := range expectedCFlags {
if group.CFlags[i] != expected {
t.Errorf("CFlags[%d] mismatch. Expected '%s', got '%s'", i, expected, group.CFlags[i])
}
}
}
// Test LDFlags and CCFlags
if len(group.LDFlags) == 0 {
t.Error("Expected non-empty LDFlags")
}
if len(group.CCFlags) == 0 {
t.Error("Expected non-empty CCFlags")
}
}
}
func TestGetPicolibcConfig_EdgeCases(t *testing.T) {
t.Run("EmptyBaseDir", func(t *testing.T) {
config := GetPicolibcConfig("", "test-target")
// Check that paths are constructed correctly even with empty baseDir
expected := "-I"
if config.LibcCFlags[0] != expected {
t.Errorf("Expected LibcCFlags[0] to be '%s', got '%s'", expected, config.LibcCFlags[0])
}
expected = "-isystem" + filepath.Join("", "newlib", "libc", "include")
if config.LibcCFlags[1] != expected {
t.Errorf("Expected LibcCFlags[1] to be '%s', got '%s'", expected, config.LibcCFlags[1])
}
})
t.Run("EmptyTarget", func(t *testing.T) {
config := GetPicolibcConfig("/test/base", "")
// Check output file name formatting
expectedOutput := "libc-.a"
if config.Groups[0].OutputFileName != expectedOutput {
t.Errorf("Expected OutputFileName '%s', got '%s'", expectedOutput, config.Groups[0].OutputFileName)
}
})
}
func TestGetNewlibESP32ConfigRISCV(t *testing.T) {
baseDir := "/test/base"
target := "riscv32-unknown-elf"
config := getNewlibESP32ConfigRISCV(baseDir, target)
// Test basic configuration
if config.Url != _newlibUrl {
t.Errorf("Expected URL '%s', got '%s'", _newlibUrl, config.Url)
}
if config.Name != "newlib-esp32" {
t.Errorf("Expected Name 'newlib-esp32', got '%s'", config.Name)
}
if config.ArchiveSrcDir != _archiveInternalSrcDir {
t.Errorf("Expected ArchiveSrcDir '%s', got '%s'", _archiveInternalSrcDir, config.ArchiveSrcDir)
}
// Test LibcCFlags
libcDir := filepath.Join(baseDir, "newlib", "libc")
expectedCFlags := []string{
"-isystem" + filepath.Join(libcDir, "include"),
"-I" + filepath.Join(baseDir, "newlib"),
"-I" + libcDir,
}
if len(config.LibcCFlags) != len(expectedCFlags) {
t.Errorf("Expected %d LibcCFlags, got %d", len(expectedCFlags), len(config.LibcCFlags))
} else {
for i, expected := range expectedCFlags {
if config.LibcCFlags[i] != expected {
t.Errorf("LibcCFlags[%d] mismatch. Expected '%s', got '%s'", i, expected, config.LibcCFlags[i])
}
}
}
// Test Groups configuration
if len(config.Groups) != 3 {
t.Errorf("Expected 3 groups, got %d", len(config.Groups))
} else {
// Group 0: libcrt0
group0 := config.Groups[0]
expectedOutput0 := "libcrt0-" + target + ".a"
if group0.OutputFileName != expectedOutput0 {
t.Errorf("Group0 OutputFileName expected '%s', got '%s'", expectedOutput0, group0.OutputFileName)
}
// Check sample files in group0
sampleFiles0 := []string{
filepath.Join(baseDir, "libgloss", "riscv", "esp", "esp_board.c"),
filepath.Join(baseDir, "libgloss", "riscv", "esp", "crt1-board.S"),
}
for _, sample := range sampleFiles0 {
found := false
for _, file := range group0.Files {
if file == sample {
found = true
break
}
}
if !found {
t.Errorf("Expected file '%s' not found in group0 files", sample)
}
}
// Group 1: libgloss
group1 := config.Groups[1]
expectedOutput1 := "libgloss-" + target + ".a"
if group1.OutputFileName != expectedOutput1 {
t.Errorf("Group1 OutputFileName expected '%s', got '%s'", expectedOutput1, group1.OutputFileName)
}
// Check sample files in group1
sampleFiles1 := []string{
filepath.Join(baseDir, "libgloss", "libnosys", "close.c"),
filepath.Join(baseDir, "libgloss", "libnosys", "sbrk.c"),
}
for _, sample := range sampleFiles1 {
found := false
for _, file := range group1.Files {
if file == sample {
found = true
break
}
}
if !found {
t.Errorf("Expected file '%s' not found in group1 files", sample)
}
}
// Group 2: libc
group2 := config.Groups[2]
expectedOutput2 := "libc-" + target + ".a"
if group2.OutputFileName != expectedOutput2 {
t.Errorf("Group2 OutputFileName expected '%s', got '%s'", expectedOutput2, group2.OutputFileName)
}
// Check sample files in group2
sampleFiles2 := []string{
filepath.Join(libcDir, "string", "memcpy.c"),
filepath.Join(libcDir, "stdlib", "malloc.c"),
}
for _, sample := range sampleFiles2 {
found := false
for _, file := range group2.Files {
if file == sample {
found = true
break
}
}
if !found {
t.Errorf("Expected file '%s' not found in group2 files", sample)
}
}
// Test CFlags for group2
expectedCFlagsGroup2 := []string{
"-DHAVE_CONFIG_H",
"-D_LIBC",
"-DHAVE_NANOSLEEP",
"-D__NO_SYSCALLS__",
// ... (other expected flags)
}
for _, expectedFlag := range expectedCFlagsGroup2 {
found := false
for _, flag := range group2.CFlags {
if flag == expectedFlag {
found = true
break
}
}
if !found {
t.Errorf("Expected flag '%s' not found in group2 CFlags", expectedFlag)
}
}
// Test LDFlags and CCFlags
if len(group0.LDFlags) == 0 {
t.Error("Expected non-empty LDFlags in group0")
}
if len(group0.CCFlags) == 0 {
t.Error("Expected non-empty CCFlags in group0")
}
}
}
func TestGetNewlibESP32ConfigXtensa(t *testing.T) {
baseDir := "/test/base"
target := "xtensa-esp32-elf"
config := getNewlibESP32ConfigXtensa(baseDir, target)
// Test basic configuration
if config.Url != _newlibUrl {
t.Errorf("Expected URL '%s', got '%s'", _newlibUrl, config.Url)
}
if config.Name != "newlib-esp32" {
t.Errorf("Expected Name 'newlib-esp32', got '%s'", config.Name)
}
if config.ArchiveSrcDir != _archiveInternalSrcDir {
t.Errorf("Expected ArchiveSrcDir '%s', got '%s'", _archiveInternalSrcDir, config.ArchiveSrcDir)
}
// Test LibcCFlags
libcDir := filepath.Join(baseDir, "newlib", "libc")
expectedCFlags := []string{
"-I" + filepath.Join(libcDir, "include"),
"-I" + filepath.Join(baseDir, "newlib"),
"-I" + libcDir,
}
if len(config.LibcCFlags) != len(expectedCFlags) {
t.Errorf("Expected %d LibcCFlags, got %d", len(expectedCFlags), len(config.LibcCFlags))
} else {
for i, expected := range expectedCFlags {
if config.LibcCFlags[i] != expected {
t.Errorf("LibcCFlags[%d] mismatch. Expected '%s', got '%s'", i, expected, config.LibcCFlags[i])
}
}
}
// Test Groups configuration
if len(config.Groups) != 3 {
t.Errorf("Expected 2 groups, got %d", len(config.Groups))
} else {
// Group 0: libcrt0
group0 := config.Groups[0]
expectedOutput0 := "libcrt0-" + target + ".a"
if group0.OutputFileName != expectedOutput0 {
t.Errorf("Group0 OutputFileName expected '%s', got '%s'", expectedOutput0, group0.OutputFileName)
}
// Check sample files in group0
sampleFiles0 := []string{
filepath.Join(baseDir, "libgloss", "xtensa", "clibrary_init.c"),
filepath.Join(baseDir, "libgloss", "xtensa", "crt1-boards.S"),
}
for _, sample := range sampleFiles0 {
found := false
for _, file := range group0.Files {
if file == sample {
found = true
break
}
}
if !found {
t.Errorf("Expected file '%s' not found in group0 files", sample)
}
}
// Group 1: libgloss + libc
group1 := config.Groups[1]
expectedOutput1 := "libgloss-" + target + ".a"
if group1.OutputFileName != expectedOutput1 {
t.Errorf("Group1 OutputFileName expected '%s', got '%s'", expectedOutput1, group1.OutputFileName)
}
// Check sample files in group1
sampleFiles1 := []string{
filepath.Join(baseDir, "libgloss", "libnosys", "close.c"),
}
for _, sample := range sampleFiles1 {
found := false
for _, file := range group1.Files {
if file == sample {
found = true
break
}
}
if !found {
t.Errorf("Expected file '%s' not found in group1 files", sample)
}
}
// Test CFlags for group1
expectedCFlagsGroup1 := []string{
"-D__NO_SYSCALLS__",
"-D_NO_GETUT",
"-DHAVE_CONFIG_H",
"-D_LIBC",
// ... (other expected flags)
}
for _, expectedFlag := range expectedCFlagsGroup1 {
found := false
for _, flag := range group1.CFlags {
if flag == expectedFlag {
found = true
break
}
}
if !found {
t.Errorf("Expected flag '%s' not found in group1 CFlags", expectedFlag)
}
}
// Test LDFlags and CCFlags
if len(group0.LDFlags) == 0 {
t.Error("Expected non-empty LDFlags in group0")
}
if len(group0.CCFlags) == 0 {
t.Error("Expected non-empty CCFlags in group0")
}
}
}
func TestEdgeCases(t *testing.T) {
t.Run("EmptyBaseDir_RISCV", func(t *testing.T) {
config := getNewlibESP32ConfigRISCV("", "test-target")
libcDir := filepath.Join("", "newlib", "libc")
// Check that paths are constructed correctly
expected := "-isystem" + filepath.Join(libcDir, "include")
if config.LibcCFlags[0] != expected {
t.Errorf("Expected LibcCFlags[0] to be '%s', got '%s'", expected, config.LibcCFlags[0])
}
})
t.Run("EmptyTarget_RISCV", func(t *testing.T) {
config := getNewlibESP32ConfigRISCV("/test/base", "")
// Check output file name formatting
expectedOutput := "libcrt0-.a"
if config.Groups[0].OutputFileName != expectedOutput {
t.Errorf("Expected OutputFileName '%s', got '%s'", expectedOutput, config.Groups[0].OutputFileName)
}
})
t.Run("EmptyBaseDir_Xtensa", func(t *testing.T) {
config := getNewlibESP32ConfigXtensa("", "test-target")
libcDir := filepath.Join("", "newlib", "libc")
// Check that paths are constructed correctly
expected := "-I" + filepath.Join(libcDir, "include")
if config.LibcCFlags[0] != expected {
t.Errorf("Expected LibcCFlags[0] to be '%s', got '%s'", expected, config.LibcCFlags[0])
}
})
t.Run("EmptyTarget_Xtensa", func(t *testing.T) {
config := getNewlibESP32ConfigXtensa("/test/base", "")
// Check output file name formatting
expectedOutput := "libcrt0-.a"
if config.Groups[0].OutputFileName != expectedOutput {
t.Errorf("Expected OutputFileName '%s', got '%s'", expectedOutput, config.Groups[0].OutputFileName)
}
})
}
func TestGroupConfiguration(t *testing.T) {
baseDir := "/test/base"
target := "test-target"
t.Run("RISCV_GroupCount", func(t *testing.T) {
config := getNewlibESP32ConfigRISCV(baseDir, target)
if len(config.Groups) != 3 {
t.Errorf("Expected 3 groups for RISCV, got %d", len(config.Groups))
}
})
t.Run("Xtensa_GroupCount", func(t *testing.T) {
config := getNewlibESP32ConfigXtensa(baseDir, target)
if len(config.Groups) != 3 {
t.Errorf("Expected 2 groups for Xtensa, got %d", len(config.Groups))
}
})
t.Run("RISCV_GroupNames", func(t *testing.T) {
config := getNewlibESP32ConfigRISCV(baseDir, target)
expectedNames := []string{
"libcrt0-" + target + ".a",
"libgloss-" + target + ".a",
"libc-" + target + ".a",
}
for i, group := range config.Groups {
if group.OutputFileName != expectedNames[i] {
t.Errorf("Group %d expected name '%s', got '%s'", i, expectedNames[i], group.OutputFileName)
}
}
})
t.Run("Xtensa_GroupNames", func(t *testing.T) {
config := getNewlibESP32ConfigXtensa(baseDir, target)
expectedNames := []string{
"libcrt0-" + target + ".a",
"libgloss-" + target + ".a",
}
for i, group := range config.Groups {
if i >= len(expectedNames) {
return
}
if group.OutputFileName != expectedNames[i] {
t.Errorf("Group %d expected name '%s', got '%s'", i, expectedNames[i], group.OutputFileName)
}
}
})
}
func TestCompilerFlags(t *testing.T) {
baseDir := "/test/base"
target := "test-target"
t.Run("RISCV_CFlags", func(t *testing.T) {
config := getNewlibESP32ConfigRISCV(baseDir, target)
group := config.Groups[2] // libc group
requiredFlags := []string{
"-DHAVE_CONFIG_H",
"-D_LIBC",
"-D__NO_SYSCALLS__",
"-isystem" + filepath.Join(baseDir, "newlib", "libc", "include"),
}
for _, flag := range requiredFlags {
found := false
for _, cflag := range group.CFlags {
if cflag == flag {
found = true
break
}
}
if !found {
t.Errorf("Required flag '%s' not found in RISCV CFlags", flag)
}
}
})
t.Run("Xtensa_CFlags", func(t *testing.T) {
config := getNewlibESP32ConfigXtensa(baseDir, target)
group := config.Groups[1] // libgloss+libc group
requiredFlags := []string{
"-D__NO_SYSCALLS__",
"-DHAVE_CONFIG_H",
"-D_LIBC",
"-isystem" + filepath.Join(baseDir, "newlib", "libc", "include"),
}
for _, flag := range requiredFlags {
found := false
for _, cflag := range group.CFlags {
if cflag == flag {
found = true
break
}
}
if !found {
t.Errorf("Required flag '%s' not found in Xtensa CFlags", flag)
}
}
})
t.Run("CommonFlags", func(t *testing.T) {
configRISCV := getNewlibESP32ConfigRISCV(baseDir, target)
configXtensa := getNewlibESP32ConfigXtensa(baseDir, target)
// Test LDFlags
expectedLDFlags := []string{
"-nostdlib",
"-ffunction-sections",
"-fdata-sections",
}
for _, group := range configRISCV.Groups {
for _, expected := range expectedLDFlags {
found := false
for _, flag := range group.LDFlags {
if flag == expected {
found = true
break
}
}
if !found {
t.Errorf("Required LDFlag '%s' not found in RISCV group", expected)
}
}
}
for _, group := range configXtensa.Groups {
for _, expected := range expectedLDFlags {
found := false
for _, flag := range group.LDFlags {
if flag == expected {
found = true
break
}
}
if !found {
t.Errorf("Required LDFlag '%s' not found in Xtensa group", expected)
}
}
}
// Test CCFlags
expectedCCFlags := []string{
"-Oz",
"-fno-builtin",
"-ffreestanding",
}
for _, group := range configRISCV.Groups {
for _, expected := range expectedCCFlags {
found := false
for _, flag := range group.CCFlags {
if flag == expected {
found = true
break
}
}
if !found {
t.Errorf("Required CCFlag '%s' not found in RISCV group", expected)
}
}
}
for _, group := range configXtensa.Groups {
for _, expected := range expectedCCFlags {
found := false
for _, flag := range group.CCFlags {
if flag == expected {
found = true
break
}
}
if !found {
t.Errorf("Required CCFlag '%s' not found in Xtensa group", expected)
}
}
}
})
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,163 @@
package libc
import (
"fmt"
"path/filepath"
"github.com/goplus/llgo/internal/crosscompile/compile"
)
// getPicolibcConfig returns configuration for picolibc
func GetPicolibcConfig(baseDir, target string) *compile.CompileConfig {
return &compile.CompileConfig{
Url: "https://github.com/goplus/picolibc/archive/refs/heads/main.zip",
Name: "picolibc",
LibcCFlags: []string{
"-I" + baseDir,
"-isystem" + filepath.Join(baseDir, "newlib", "libc", "include"),
},
Groups: []compile.CompileGroup{
{
OutputFileName: fmt.Sprintf("libc-%s.a", target),
Files: []string{
filepath.Join(baseDir, "newlib", "libc", "string", "bcmp.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "bcopy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "bzero.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "explicit_bzero.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "ffsl.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "ffsll.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "fls.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "flsl.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "flsll.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "gnu_basename.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "index.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "memccpy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "memchr.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "memcmp.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "memcpy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "memmem.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "memmove.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "mempcpy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "memrchr.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "memset.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "rawmemchr.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "rindex.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "stpcpy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "stpncpy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strcasecmp.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strcasecmp_l.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strcasestr.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strcat.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strchr.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strchrnul.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strcmp.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strcoll.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strcoll_l.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strcpy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strcspn.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strerror_r.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strlcat.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strlcpy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strlen.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strlwr.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strncasecmp.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strncasecmp_l.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strncat.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strncmp.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strncpy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strndup.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strnlen.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strnstr.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strpbrk.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strrchr.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strsep.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strsignal.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strspn.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strstr.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strtok.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strtok_r.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strupr.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strverscmp.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strxfrm.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strxfrm_l.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "swab.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "timingsafe_bcmp.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "timingsafe_memcmp.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "strerror.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcpcpy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcpncpy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcscasecmp.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcscasecmp_l.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcscat.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcschr.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcscmp.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcscoll.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcscoll_l.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcscpy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcscspn.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcsdup.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcslcat.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcslcpy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcslen.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcsncasecmp.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcsncasecmp_l.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcsncat.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcsncmp.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcsncpy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcsnlen.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcspbrk.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcsrchr.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcsspn.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcsstr.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcstok.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcswidth.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcsxfrm.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcsxfrm_l.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wcwidth.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wmemchr.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wmemcmp.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wmemcpy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wmemmove.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wmempcpy.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "wmemset.c"),
filepath.Join(baseDir, "newlib", "libc", "string", "xpg_strerror_r.c"),
filepath.Join(baseDir, "newlib", "libc", "stdlib", "nano-calloc.c"),
filepath.Join(baseDir, "newlib", "libc", "stdlib", "nano-malloc.c"),
filepath.Join(baseDir, "newlib", "libc", "stdlib", "nano-pvalloc.c"),
filepath.Join(baseDir, "newlib", "libc", "stdlib", "nano-realloc.c"),
filepath.Join(baseDir, "newlib", "libc", "stdlib", "nano-valloc.c"),
filepath.Join(baseDir, "newlib", "libc", "stdlib", "rand.c"),
filepath.Join(baseDir, "newlib", "libc", "stdlib", "srand.c"),
filepath.Join(baseDir, "newlib", "libc", "stdlib", "nano-free.c"),
filepath.Join(baseDir, "newlib", "libc", "tinystdio", "printf.c"),
filepath.Join(baseDir, "newlib", "libc", "tinystdio", "putchar.c"),
filepath.Join(baseDir, "newlib", "libc", "tinystdio", "puts.c"),
},
CFlags: []string{
"-D_COMPILING_NEWLIB",
"-D_HAVE_ALIAS_ATTRIBUTE",
"-DTINY_STDIO",
"-DPOSIX_IO",
"-DFORMAT_DEFAULT_INTEGER",
"-D_IEEE_LIBM",
"-D__OBSOLETE_MATH_FLOAT=1",
"-D__OBSOLETE_MATH_DOUBLE=0",
"-D_WANT_IO_C99_FORMATS",
"-nostdlib",
"-I" + baseDir,
"-isystem" + filepath.Join(baseDir, "newlib", "libc", "include"),
"-I" + filepath.Join(baseDir, "newlib", "libm", "common"),
"-I" + filepath.Join(baseDir, "newlib", "libc", "locale"),
"-I" + filepath.Join(baseDir, "newlib", "libc", "tinystdio"),
},
LDFlags: _libcLDFlags,
CCFlags: _libcCCFlags,
},
},
ArchiveSrcDir: "picolibc-main",
}
}

View File

@@ -0,0 +1,288 @@
package rtlib
import (
"fmt"
"path/filepath"
"strings"
"github.com/goplus/llgo/internal/crosscompile/compile"
)
func platformSpecifiedFiles(builtinsDir, target string) []string {
switch {
case strings.Contains(target, "riscv32"):
return []string{
filepath.Join(builtinsDir, "riscv", "mulsi3.S"),
filepath.Join(builtinsDir, "riscv", "fp_mode.c"),
filepath.Join(builtinsDir, "riscv", "save.S"),
filepath.Join(builtinsDir, "riscv", "restore.S"),
filepath.Join(builtinsDir, "atomic.c"),
}
case strings.Contains(target, "riscv64"):
return []string{
filepath.Join(builtinsDir, "addtf3.c"),
filepath.Join(builtinsDir, "comparetf2.c"),
filepath.Join(builtinsDir, "divtc3.c"),
filepath.Join(builtinsDir, "divtf3.c"),
filepath.Join(builtinsDir, "extenddftf2.c"),
filepath.Join(builtinsDir, "extendhftf2.c"),
filepath.Join(builtinsDir, "extendsftf2.c"),
filepath.Join(builtinsDir, "fixtfdi.c"),
filepath.Join(builtinsDir, "fixtfsi.c"),
filepath.Join(builtinsDir, "fixtfti.c"),
filepath.Join(builtinsDir, "fixunstfdi.c"),
filepath.Join(builtinsDir, "fixunstfsi.c"),
filepath.Join(builtinsDir, "fixunstfti.c"),
filepath.Join(builtinsDir, "floatditf.c"),
filepath.Join(builtinsDir, "floatsitf.c"),
filepath.Join(builtinsDir, "floattitf.c"),
filepath.Join(builtinsDir, "floatunditf.c"),
filepath.Join(builtinsDir, "floatunsitf.c"),
filepath.Join(builtinsDir, "floatuntitf.c"),
filepath.Join(builtinsDir, "multc3.c"),
filepath.Join(builtinsDir, "multf3.c"),
filepath.Join(builtinsDir, "powitf2.c"),
filepath.Join(builtinsDir, "subtf3.c"),
filepath.Join(builtinsDir, "trunctfdf2.c"),
filepath.Join(builtinsDir, "trunctfhf2.c"),
filepath.Join(builtinsDir, "trunctfsf2.c"),
filepath.Join(builtinsDir, "atomic.c"),
}
case strings.Contains(target, "arm"):
return []string{
filepath.Join(builtinsDir, "arm", "aeabi_cdcmp.S"),
filepath.Join(builtinsDir, "arm", "aeabi_cdcmpeq_check_nan.c"),
filepath.Join(builtinsDir, "arm", "aeabi_cfcmp.S"),
filepath.Join(builtinsDir, "arm", "aeabi_cfcmpeq_check_nan.c"),
filepath.Join(builtinsDir, "arm", "aeabi_dcmp.S"),
filepath.Join(builtinsDir, "arm", "aeabi_div0.c"),
filepath.Join(builtinsDir, "arm", "aeabi_drsub.c"),
filepath.Join(builtinsDir, "arm", "aeabi_fcmp.S"),
filepath.Join(builtinsDir, "arm", "aeabi_frsub.c"),
filepath.Join(builtinsDir, "arm", "aeabi_idivmod.S"),
filepath.Join(builtinsDir, "arm", "aeabi_ldivmod.S"),
filepath.Join(builtinsDir, "arm", "aeabi_memcmp.S"),
filepath.Join(builtinsDir, "arm", "aeabi_memcpy.S"),
filepath.Join(builtinsDir, "arm", "aeabi_memmove.S"),
filepath.Join(builtinsDir, "arm", "aeabi_memset.S"),
filepath.Join(builtinsDir, "arm", "aeabi_uidivmod.S"),
filepath.Join(builtinsDir, "arm", "aeabi_uldivmod.S"),
// These two are not technically EABI builtins but are used by them and only
// seem to be used on ARM. LLVM seems to use __divsi3 and __modsi3 on most
// other architectures.
// Most importantly, they have a different calling convention on AVR so
// should not be used on AVR.
filepath.Join(builtinsDir, "divmodsi4.c"),
filepath.Join(builtinsDir, "udivmodsi4.c"),
}
case strings.Contains(target, "avr"):
return []string{
filepath.Join(builtinsDir, "avr", "divmodhi4.S"),
filepath.Join(builtinsDir, "avr", "divmodqi4.S"),
filepath.Join(builtinsDir, "avr", "mulhi3.S"),
filepath.Join(builtinsDir, "avr", "mulqi3.S"),
filepath.Join(builtinsDir, "avr", "udivmodhi4.S"),
filepath.Join(builtinsDir, "avr", "udivmodqi4.S"),
}
case target == "xtensa":
return []string{
filepath.Join(builtinsDir, "xtensa", "ieee754_sqrtf.S"),
filepath.Join(builtinsDir, "atomic.c"),
}
}
return nil
}
func withPlatformSpecifiedFiles(baseDir, target string, files []string) []string {
builtinsDir := filepath.Join(baseDir, "lib", "builtins")
return append(files, platformSpecifiedFiles(builtinsDir, target)...)
}
func GetCompilerRTConfig(baseDir, target string) *compile.CompileConfig {
return &compile.CompileConfig{
Url: "https://github.com/goplus/compiler-rt/archive/refs/tags/v0.1.0.tar.gz",
ArchiveSrcDir: "compiler-rt-0.1.0",
Groups: []compile.CompileGroup{
{
OutputFileName: fmt.Sprintf("libclang_builtins-%s.a", target),
Files: withPlatformSpecifiedFiles(baseDir, target, []string{
filepath.Join(baseDir, "lib", "builtins", "absvdi2.c"),
filepath.Join(baseDir, "lib", "builtins", "absvsi2.c"),
filepath.Join(baseDir, "lib", "builtins", "absvti2.c"),
filepath.Join(baseDir, "lib", "builtins", "adddf3.c"),
filepath.Join(baseDir, "lib", "builtins", "addsf3.c"),
filepath.Join(baseDir, "lib", "builtins", "addvdi3.c"),
filepath.Join(baseDir, "lib", "builtins", "addvsi3.c"),
filepath.Join(baseDir, "lib", "builtins", "addvti3.c"),
filepath.Join(baseDir, "lib", "builtins", "apple_versioning.c"),
filepath.Join(baseDir, "lib", "builtins", "ashldi3.c"),
filepath.Join(baseDir, "lib", "builtins", "ashlti3.c"),
filepath.Join(baseDir, "lib", "builtins", "ashrdi3.c"),
filepath.Join(baseDir, "lib", "builtins", "ashrti3.c"),
filepath.Join(baseDir, "lib", "builtins", "bswapdi2.c"),
filepath.Join(baseDir, "lib", "builtins", "bswapsi2.c"),
filepath.Join(baseDir, "lib", "builtins", "clzdi2.c"),
filepath.Join(baseDir, "lib", "builtins", "clzsi2.c"),
filepath.Join(baseDir, "lib", "builtins", "clzti2.c"),
filepath.Join(baseDir, "lib", "builtins", "cmpdi2.c"),
filepath.Join(baseDir, "lib", "builtins", "cmpti2.c"),
filepath.Join(baseDir, "lib", "builtins", "comparedf2.c"),
filepath.Join(baseDir, "lib", "builtins", "comparesf2.c"),
filepath.Join(baseDir, "lib", "builtins", "ctzdi2.c"),
filepath.Join(baseDir, "lib", "builtins", "ctzsi2.c"),
filepath.Join(baseDir, "lib", "builtins", "ctzti2.c"),
filepath.Join(baseDir, "lib", "builtins", "divdc3.c"),
filepath.Join(baseDir, "lib", "builtins", "divdf3.c"),
filepath.Join(baseDir, "lib", "builtins", "divdi3.c"),
filepath.Join(baseDir, "lib", "builtins", "divmoddi4.c"),
filepath.Join(baseDir, "lib", "builtins", "divmodsi4.c"),
filepath.Join(baseDir, "lib", "builtins", "divmodti4.c"),
filepath.Join(baseDir, "lib", "builtins", "divsc3.c"),
filepath.Join(baseDir, "lib", "builtins", "divsf3.c"),
filepath.Join(baseDir, "lib", "builtins", "divsi3.c"),
filepath.Join(baseDir, "lib", "builtins", "divti3.c"),
filepath.Join(baseDir, "lib", "builtins", "extendsfdf2.c"),
filepath.Join(baseDir, "lib", "builtins", "extendhfsf2.c"),
filepath.Join(baseDir, "lib", "builtins", "ffsdi2.c"),
filepath.Join(baseDir, "lib", "builtins", "ffssi2.c"),
filepath.Join(baseDir, "lib", "builtins", "ffsti2.c"),
filepath.Join(baseDir, "lib", "builtins", "fixdfdi.c"),
filepath.Join(baseDir, "lib", "builtins", "fixdfsi.c"),
filepath.Join(baseDir, "lib", "builtins", "fixdfti.c"),
filepath.Join(baseDir, "lib", "builtins", "fixsfdi.c"),
filepath.Join(baseDir, "lib", "builtins", "fixsfsi.c"),
filepath.Join(baseDir, "lib", "builtins", "fixsfti.c"),
filepath.Join(baseDir, "lib", "builtins", "fixunsdfdi.c"),
filepath.Join(baseDir, "lib", "builtins", "fixunsdfsi.c"),
filepath.Join(baseDir, "lib", "builtins", "fixunsdfti.c"),
filepath.Join(baseDir, "lib", "builtins", "fixunssfdi.c"),
filepath.Join(baseDir, "lib", "builtins", "fixunssfsi.c"),
filepath.Join(baseDir, "lib", "builtins", "fixunssfti.c"),
filepath.Join(baseDir, "lib", "builtins", "floatdidf.c"),
filepath.Join(baseDir, "lib", "builtins", "floatdisf.c"),
filepath.Join(baseDir, "lib", "builtins", "floatsidf.c"),
filepath.Join(baseDir, "lib", "builtins", "floatsisf.c"),
filepath.Join(baseDir, "lib", "builtins", "floattidf.c"),
filepath.Join(baseDir, "lib", "builtins", "floattisf.c"),
filepath.Join(baseDir, "lib", "builtins", "floatundidf.c"),
filepath.Join(baseDir, "lib", "builtins", "floatundisf.c"),
filepath.Join(baseDir, "lib", "builtins", "floatunsidf.c"),
filepath.Join(baseDir, "lib", "builtins", "floatunsisf.c"),
filepath.Join(baseDir, "lib", "builtins", "floatuntidf.c"),
filepath.Join(baseDir, "lib", "builtins", "floatuntisf.c"),
filepath.Join(baseDir, "lib", "builtins", "fp_mode.c"),
filepath.Join(baseDir, "lib", "builtins", "int_util.c"),
filepath.Join(baseDir, "lib", "builtins", "lshrdi3.c"),
filepath.Join(baseDir, "lib", "builtins", "lshrti3.c"),
filepath.Join(baseDir, "lib", "builtins", "moddi3.c"),
filepath.Join(baseDir, "lib", "builtins", "modsi3.c"),
filepath.Join(baseDir, "lib", "builtins", "modti3.c"),
filepath.Join(baseDir, "lib", "builtins", "muldc3.c"),
filepath.Join(baseDir, "lib", "builtins", "muldf3.c"),
filepath.Join(baseDir, "lib", "builtins", "muldi3.c"),
filepath.Join(baseDir, "lib", "builtins", "mulodi4.c"),
filepath.Join(baseDir, "lib", "builtins", "mulosi4.c"),
filepath.Join(baseDir, "lib", "builtins", "muloti4.c"),
filepath.Join(baseDir, "lib", "builtins", "mulsc3.c"),
filepath.Join(baseDir, "lib", "builtins", "mulsf3.c"),
filepath.Join(baseDir, "lib", "builtins", "multi3.c"),
filepath.Join(baseDir, "lib", "builtins", "mulvdi3.c"),
filepath.Join(baseDir, "lib", "builtins", "mulvsi3.c"),
filepath.Join(baseDir, "lib", "builtins", "mulvti3.c"),
filepath.Join(baseDir, "lib", "builtins", "negdf2.c"),
filepath.Join(baseDir, "lib", "builtins", "negdi2.c"),
filepath.Join(baseDir, "lib", "builtins", "negsf2.c"),
filepath.Join(baseDir, "lib", "builtins", "negti2.c"),
filepath.Join(baseDir, "lib", "builtins", "negvdi2.c"),
filepath.Join(baseDir, "lib", "builtins", "negvsi2.c"),
filepath.Join(baseDir, "lib", "builtins", "negvti2.c"),
filepath.Join(baseDir, "lib", "builtins", "os_version_check.c"),
filepath.Join(baseDir, "lib", "builtins", "paritydi2.c"),
filepath.Join(baseDir, "lib", "builtins", "paritysi2.c"),
filepath.Join(baseDir, "lib", "builtins", "parityti2.c"),
filepath.Join(baseDir, "lib", "builtins", "popcountdi2.c"),
filepath.Join(baseDir, "lib", "builtins", "popcountsi2.c"),
filepath.Join(baseDir, "lib", "builtins", "popcountti2.c"),
filepath.Join(baseDir, "lib", "builtins", "powidf2.c"),
filepath.Join(baseDir, "lib", "builtins", "powisf2.c"),
filepath.Join(baseDir, "lib", "builtins", "subdf3.c"),
filepath.Join(baseDir, "lib", "builtins", "subsf3.c"),
filepath.Join(baseDir, "lib", "builtins", "subvdi3.c"),
filepath.Join(baseDir, "lib", "builtins", "subvsi3.c"),
filepath.Join(baseDir, "lib", "builtins", "subvti3.c"),
filepath.Join(baseDir, "lib", "builtins", "trampoline_setup.c"),
filepath.Join(baseDir, "lib", "builtins", "truncdfhf2.c"),
filepath.Join(baseDir, "lib", "builtins", "truncdfsf2.c"),
filepath.Join(baseDir, "lib", "builtins", "truncsfhf2.c"),
filepath.Join(baseDir, "lib", "builtins", "ucmpdi2.c"),
filepath.Join(baseDir, "lib", "builtins", "ucmpti2.c"),
filepath.Join(baseDir, "lib", "builtins", "udivdi3.c"),
filepath.Join(baseDir, "lib", "builtins", "udivmoddi4.c"),
filepath.Join(baseDir, "lib", "builtins", "udivmodsi4.c"),
filepath.Join(baseDir, "lib", "builtins", "udivmodti4.c"),
filepath.Join(baseDir, "lib", "builtins", "udivsi3.c"),
filepath.Join(baseDir, "lib", "builtins", "udivti3.c"),
filepath.Join(baseDir, "lib", "builtins", "umoddi3.c"),
filepath.Join(baseDir, "lib", "builtins", "umodsi3.c"),
filepath.Join(baseDir, "lib", "builtins", "umodti3.c"),
filepath.Join(baseDir, "lib", "builtins", "gcc_personality_v0.c"),
filepath.Join(baseDir, "lib", "builtins", "clear_cache.c"),
filepath.Join(baseDir, "lib", "builtins", "addtf3.c"),
filepath.Join(baseDir, "lib", "builtins", "comparetf2.c"),
filepath.Join(baseDir, "lib", "builtins", "divtc3.c"),
filepath.Join(baseDir, "lib", "builtins", "divtf3.c"),
filepath.Join(baseDir, "lib", "builtins", "extenddftf2.c"),
filepath.Join(baseDir, "lib", "builtins", "extendhftf2.c"),
filepath.Join(baseDir, "lib", "builtins", "extendsftf2.c"),
filepath.Join(baseDir, "lib", "builtins", "fixtfdi.c"),
filepath.Join(baseDir, "lib", "builtins", "fixtfsi.c"),
filepath.Join(baseDir, "lib", "builtins", "fixtfti.c"),
filepath.Join(baseDir, "lib", "builtins", "fixunstfdi.c"),
filepath.Join(baseDir, "lib", "builtins", "fixunstfsi.c"),
filepath.Join(baseDir, "lib", "builtins", "fixunstfti.c"),
filepath.Join(baseDir, "lib", "builtins", "floatditf.c"),
filepath.Join(baseDir, "lib", "builtins", "floatsitf.c"),
filepath.Join(baseDir, "lib", "builtins", "floattitf.c"),
filepath.Join(baseDir, "lib", "builtins", "floatunditf.c"),
filepath.Join(baseDir, "lib", "builtins", "floatunsitf.c"),
filepath.Join(baseDir, "lib", "builtins", "floatuntitf.c"),
filepath.Join(baseDir, "lib", "builtins", "multc3.c"),
filepath.Join(baseDir, "lib", "builtins", "multf3.c"),
filepath.Join(baseDir, "lib", "builtins", "powitf2.c"),
filepath.Join(baseDir, "lib", "builtins", "subtf3.c"),
filepath.Join(baseDir, "lib", "builtins", "trunctfdf2.c"),
filepath.Join(baseDir, "lib", "builtins", "trunctfhf2.c"),
filepath.Join(baseDir, "lib", "builtins", "trunctfsf2.c"),
}),
CFlags: []string{
"-DNDEBUG",
"-DVISIBILITY_HIDDEN",
},
CCFlags: []string{
"-Oz",
"-fno-ident",
"-Wno-unused-parameter",
"-fno-lto",
"-Werror=array-bounds",
"-Werror=uninitialized",
"-Werror=shadow",
"-Werror=empty-body",
"-Werror=sizeof-pointer-memaccess",
"-Werror=sizeof-array-argument",
"-Werror=suspicious-memaccess",
"-Werror=builtin-memcpy-chk-size",
"-Werror=array-bounds-pointer-arithmetic",
"-Werror=return-stack-address",
"-Werror=sizeof-array-decay",
"-Werror=format-insufficient-args",
"-Wformat -std=c11",
"-fno-builtin",
"-fvisibility=hidden",
"-fomit-frame-pointer",
},
},
},
}
}

View File

@@ -0,0 +1,124 @@
package rtlib
import (
"strings"
"testing"
)
func TestPlatformSpecifiedFiles(t *testing.T) {
tests := []struct {
target string
expected int // Number of expected files
}{
{"riscv32-unknown-elf", 5},
{"riscv64-unknown-elf", 27},
{"arm-none-eabi", 19},
{"avr-unknown-elf", 6},
{"xtensa", 2},
{"x86_64-pc-windows", 0},
}
builtinsDir := "/test/builtins"
for _, tt := range tests {
t.Run(tt.target, func(t *testing.T) {
result := platformSpecifiedFiles(builtinsDir, tt.target)
if len(result) != tt.expected {
t.Errorf("For target %s, expected %d files, got %d", tt.target, tt.expected, len(result))
}
})
}
}
func TestWithPlatformSpecifiedFiles(t *testing.T) {
baseDir := "/test/base"
target := "riscv32-unknown-elf"
inputFiles := []string{"file1.c", "file2.c"}
result := withPlatformSpecifiedFiles(baseDir, target, inputFiles)
// Should have input files + platform specific files
if len(result) <= len(inputFiles) {
t.Errorf("Expected more files than input, got %d", len(result))
}
// Check that input files are preserved
for _, inputFile := range inputFiles {
found := false
for _, resultFile := range result {
if resultFile == inputFile {
found = true
break
}
}
if !found {
t.Errorf("Input file %s not found in result", inputFile)
}
}
}
func TestGetCompilerRTConfig(t *testing.T) {
baseDir := "/test/base"
target := "riscv32-unknown-elf"
config := GetCompilerRTConfig(baseDir, target)
// Test groups configuration
if len(config.Groups) != 1 {
t.Errorf("Expected 1 group, got %d", len(config.Groups))
} else {
group := config.Groups[0]
expectedOutput := "libclang_builtins-" + target + ".a"
if group.OutputFileName != expectedOutput {
t.Errorf("Expected output file %s, got %s", expectedOutput, group.OutputFileName)
}
// Check that files list contains platform-specific files
if len(group.Files) == 0 {
t.Error("Expected non-empty files list")
}
// Check that CFlags are set
if len(group.CFlags) == 0 {
t.Error("Expected non-empty CFlags")
}
// Check that CCFlags are set
if len(group.CCFlags) == 0 {
t.Error("Expected non-empty CCFlags")
}
}
}
func TestGetCompilerRTConfig_DifferentTargets(t *testing.T) {
targets := []string{
"riscv32-unknown-elf",
"riscv64-unknown-elf",
"arm-none-eabi",
"avr-unknown-elf",
"xtensa",
}
baseDir := "/test/base"
for _, target := range targets {
t.Run(target, func(t *testing.T) {
config := GetCompilerRTConfig(baseDir, target)
// Basic validation
if config.Url == "" {
t.Error("URL should not be empty")
}
if config.ArchiveSrcDir == "" {
t.Error("ArchiveSrcDir should not be empty")
}
if len(config.Groups) == 0 {
t.Error("Should have at least one group")
}
// Check output filename contains target
group := config.Groups[0]
if !strings.Contains(group.OutputFileName, target) {
t.Errorf("Output filename %s should contain target %s", group.OutputFileName, target)
}
})
}
}

View File

@@ -10,6 +10,7 @@ import (
"runtime"
"strings"
"github.com/goplus/llgo/internal/crosscompile/compile"
"github.com/goplus/llgo/internal/env"
"github.com/goplus/llgo/internal/targets"
"github.com/goplus/llgo/internal/xtool/llvm"
@@ -25,6 +26,7 @@ type Export struct {
BuildTags []string
GOOS string
GOARCH string
Libc string
Linker string // Linker to use (e.g., "ld.lld", "avr-ld")
ExtraFiles []string // Extra files to compile and link (e.g., .s, .c files)
ClangRoot string // Root directory of custom clang installation
@@ -215,6 +217,35 @@ func getESPClangPlatform(goos, goarch string) string {
return ""
}
func ldFlagsFromFileName(fileName string) string {
return strings.TrimPrefix(strings.TrimSuffix(fileName, ".a"), "lib")
}
func getOrCompileWithConfig(
compileConfig *compile.CompileConfig,
outputDir string, options compile.CompileOptions,
) (ldflags []string, err error) {
if err = checkDownloadAndExtractLib(
compileConfig.Url, outputDir,
compileConfig.ArchiveSrcDir,
); err != nil {
return
}
ldflags = append(ldflags, "-nostdlib", "-L"+outputDir)
for _, group := range compileConfig.Groups {
err = group.Compile(outputDir, options)
if err != nil {
break
}
if filepath.Ext(group.OutputFileName) == ".o" {
continue
}
ldflags = append(ldflags, "-l"+ldFlagsFromFileName(group.OutputFileName))
}
return
}
func use(goos, goarch string, wasiThreads bool) (export Export, err error) {
targetTriple := llvm.GetTargetTriple(goos, goarch)
llgoRoot := env.LLGoROOT()
@@ -574,6 +605,57 @@ func useTarget(targetName string) (export Export, err error) {
}
ldflags = append(ldflags, "-L", env.LLGoROOT()) // search targets/*.ld
var libcIncludeDir []string
if config.Libc != "" {
var libcLDFlags []string
var compileConfig *compile.CompileConfig
baseDir := filepath.Join(cacheRoot(), "crosscompile")
outputDir := filepath.Join(baseDir, config.Libc)
compileConfig, err = getLibcCompileConfigByName(baseDir, config.Libc, config.LLVMTarget, config.CPU)
if err != nil {
return
}
libcLDFlags, err = getOrCompileWithConfig(compileConfig, outputDir, compile.CompileOptions{
CC: export.CC,
Linker: export.Linker,
CCFLAGS: ccflags,
LDFLAGS: ldflags,
})
if err != nil {
return
}
cflags = append(cflags, compileConfig.LibcCFlags...)
ldflags = append(ldflags, libcLDFlags...)
libcIncludeDir = compileConfig.LibcCFlags
export.Libc = config.Libc
}
if config.RTLib != "" {
var rtLibLDFlags []string
var compileConfig *compile.CompileConfig
baseDir := filepath.Join(cacheRoot(), "crosscompile")
outputDir := filepath.Join(baseDir, config.RTLib)
compileConfig, err = getRTCompileConfigByName(baseDir, config.RTLib, config.LLVMTarget)
if err != nil {
return
}
rtLibLDFlags, err = getOrCompileWithConfig(compileConfig, outputDir, compile.CompileOptions{
CC: export.CC,
Linker: export.Linker,
CCFLAGS: ccflags,
LDFLAGS: ldflags,
CFLAGS: libcIncludeDir,
})
if err != nil {
return
}
ldflags = append(ldflags, rtLibLDFlags...)
}
// Combine with config flags and expand template variables
export.CFLAGS = cflags
export.CCFLAGS = ccflags
@@ -586,7 +668,7 @@ func useTarget(targetName string) (export Export, err error) {
// Use extends the original Use function to support target-based configuration
// If targetName is provided, it takes precedence over goos/goarch
func Use(goos, goarch string, wasiThreads bool, targetName string) (export Export, err error) {
if targetName != "" {
if targetName != "" && !strings.HasPrefix(targetName, "wasm") && !strings.HasPrefix(targetName, "wasi") {
return useTarget(targetName)
}
return use(goos, goarch, wasiThreads)

View File

@@ -176,13 +176,14 @@ func TestUseTarget(t *testing.T) {
expectLLVM string
expectCPU string
}{
{
name: "WASI Target",
targetName: "wasi",
expectError: false,
expectLLVM: "",
expectCPU: "generic",
},
// FIXME(MeteorsLiu): wasi in useTarget
// {
// name: "WASI Target",
// targetName: "wasi",
// expectError: false,
// expectLLVM: "",
// expectCPU: "generic",
// },
{
name: "RP2040 Target",
targetName: "rp2040",
@@ -279,13 +280,13 @@ func TestUseTarget(t *testing.T) {
func TestUseWithTarget(t *testing.T) {
// Test target-based configuration takes precedence
export, err := Use("linux", "amd64", false, "wasi")
export, err := Use("linux", "amd64", false, "esp32")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Check if LLVM target is in CCFLAGS
found := slices.Contains(export.CCFLAGS, "-mcpu=generic")
found := slices.Contains(export.CCFLAGS, "-mcpu=esp32")
if !found {
t.Errorf("Expected CPU generic in CCFLAGS, got %v", export.CCFLAGS)
}

View File

@@ -2,12 +2,14 @@ package crosscompile
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"syscall"
@@ -78,6 +80,48 @@ func checkDownloadAndExtractESPClang(platformSuffix, dir string) error {
return nil
}
func checkDownloadAndExtractLib(url, dstDir, internalArchiveSrcDir string) error {
// Check if already exists
if _, err := os.Stat(dstDir); err == nil {
return nil
}
// Create lock file path for the final destination
lockPath := dstDir + ".lock"
lockFile, err := acquireLock(lockPath)
if err != nil {
return fmt.Errorf("failed to acquire lock: %w", err)
}
defer releaseLock(lockFile)
// Double-check after acquiring lock
if _, err := os.Stat(dstDir); err == nil {
return nil
}
fmt.Fprintf(os.Stderr, "%s not found in LLGO_ROOT or cache, will download and compile.\n", dstDir)
description := fmt.Sprintf("lib %s", path.Base(url))
// Use temporary extraction directory
tempExtractDir := dstDir + ".extract"
if err := downloadAndExtractArchive(url, tempExtractDir, description); err != nil {
return err
}
defer os.RemoveAll(tempExtractDir)
srcDir := tempExtractDir
if internalArchiveSrcDir != "" {
srcDir = filepath.Join(tempExtractDir, internalArchiveSrcDir)
}
if err := os.Rename(srcDir, dstDir); err != nil {
return fmt.Errorf("failed to rename lib directory: %w", err)
}
return nil
}
// acquireLock creates and locks a file to prevent concurrent operations
func acquireLock(lockPath string) (*os.File, error) {
// Ensure the parent directory exists
@@ -140,10 +184,14 @@ func downloadAndExtractArchive(url, destDir, description string) error {
if err != nil {
return fmt.Errorf("failed to extract %s archive: %w", description, err)
}
} else if strings.HasSuffix(filename, ".zip") {
err := extractZip(localFile, tempDir)
if err != nil {
return fmt.Errorf("failed to extract %s archive: %w", description, err)
}
} else {
return fmt.Errorf("unsupported archive format: %s", filename)
}
// Rename temp directory to target directory
if err := os.Rename(tempDir, destDir); err != nil {
return fmt.Errorf("failed to rename directory: %w", err)
@@ -223,3 +271,44 @@ func extractTarXz(tarXzFile, dest string) error {
cmd := exec.Command("tar", "-xf", tarXzFile, "-C", dest)
return cmd.Run()
}
func extractZip(zipFile, dest string) error {
r, err := zip.OpenReader(zipFile)
if err != nil {
return err
}
defer r.Close()
decompress := func(file *zip.File) error {
path := filepath.Join(dest, file.Name)
if file.FileInfo().IsDir() {
return os.MkdirAll(path, 0700)
}
fs, err := file.Open()
if err != nil {
return err
}
defer fs.Close()
w, err := os.Create(path)
if err != nil {
return err
}
if _, err := io.Copy(w, fs); err != nil {
w.Close()
return err
}
if err := w.Close(); err != nil {
return err
}
return nil
}
for _, file := range r.File {
if err = decompress(file); err != nil {
break
}
}
return err
}

View File

@@ -5,10 +5,13 @@ package crosscompile
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"fmt"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
@@ -287,14 +290,14 @@ func TestDownloadAndExtractArchive(t *testing.T) {
func TestDownloadAndExtractArchiveUnsupportedFormat(t *testing.T) {
server := createTestServer(t, map[string]string{
"test.zip": "fake zip content",
"test.7z": "fake zip content",
})
defer server.Close()
tempDir := t.TempDir()
destDir := filepath.Join(tempDir, "extracted")
err := downloadAndExtractArchive(server.URL+"/test.zip", destDir, "Test Archive")
err := downloadAndExtractArchive(server.URL+"/test.7z", destDir, "Test Archive")
if err == nil {
t.Error("Expected error for unsupported format, got nil")
}
@@ -303,6 +306,162 @@ func TestDownloadAndExtractArchiveUnsupportedFormat(t *testing.T) {
}
}
func TestCheckDownloadAndExtractLib(t *testing.T) {
files := map[string]string{
"lib-src/file1.c": "int func1() { return 1; }",
"lib-src/file2.c": "int func2() { return 2; }",
"lib-src/include/lib.h": "#define LIB_VERSION 1",
}
archivePath := createTestTarGz(t, files)
defer os.Remove(archivePath)
archiveContent, err := os.ReadFile(archivePath)
if err != nil {
t.Fatalf("Failed to read test archive: %v", err)
}
server := createTestServer(t, map[string]string{
"test-lib.tar.gz": string(archiveContent),
})
defer server.Close()
tempDir := t.TempDir()
destDir := filepath.Join(tempDir, "test-lib")
t.Run("LibAlreadyExists", func(t *testing.T) {
if err := os.MkdirAll(destDir, 0755); err != nil {
t.Fatalf("Failed to create existing lib dir: %v", err)
}
err := checkDownloadAndExtractLib(server.URL+"/test-lib.tar.gz", destDir, "")
if err != nil {
t.Errorf("Expected no error when lib exists, got: %v", err)
}
})
t.Run("DownloadAndExtractWithoutInternalDir", func(t *testing.T) {
os.RemoveAll(destDir)
err := checkDownloadAndExtractLib(server.URL+"/test-lib.tar.gz", destDir, "lib-src")
if err != nil {
t.Fatalf("Failed to download and extract lib: %v", err)
}
cmd := exec.Command("ls", destDir)
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
cmd.Run()
for name, expectedContent := range files {
relPath := strings.TrimPrefix(name, "lib-src/")
filePath := filepath.Join(destDir, relPath)
fmt.Println(filePath, destDir)
content, err := os.ReadFile(filePath)
if err != nil {
t.Errorf("Failed to read extracted file %s: %v", relPath, err)
continue
}
if string(content) != expectedContent {
t.Errorf("File %s: expected content %q, got %q", relPath, expectedContent, string(content))
}
}
})
t.Run("DownloadAndExtractWithInternalDir", func(t *testing.T) {
os.RemoveAll(destDir)
err := checkDownloadAndExtractLib(server.URL+"/test-lib.tar.gz", destDir, "lib-src")
if err != nil {
t.Fatalf("Failed to download and extract lib: %v", err)
}
for name, expectedContent := range files {
relPath := strings.TrimPrefix(name, "lib-src/")
filePath := filepath.Join(destDir, relPath)
content, err := os.ReadFile(filePath)
if err != nil {
t.Errorf("Failed to read extracted file %s: %v", relPath, err)
continue
}
if string(content) != expectedContent {
t.Errorf("File %s: expected content %q, got %q", relPath, expectedContent, string(content))
}
}
})
t.Run("DownloadFailure", func(t *testing.T) {
os.RemoveAll(destDir)
err := checkDownloadAndExtractLib(server.URL+"/nonexistent.tar.gz", destDir, "")
if err == nil {
t.Error("Expected error for non-existent archive, got nil")
}
})
t.Run("RenameFailure", func(t *testing.T) {
os.RemoveAll(destDir)
err := checkDownloadAndExtractLib(server.URL+"/test-lib.tar.gz", destDir, "lib-src222")
if err == nil {
t.Error("Expected error for rename failure, got nil")
}
})
}
func TestCheckDownloadAndExtractLibInternalDir(t *testing.T) {
files := map[string]string{
"project-1.0.0/src/file1.c": "int func1() { return 1; }",
"project-1.0.0/include/lib.h": "#define LIB_VERSION 1",
"project-1.0.0/README.md": "Project documentation",
}
archivePath := createTestTarGz(t, files)
defer os.Remove(archivePath)
archiveContent, err := os.ReadFile(archivePath)
if err != nil {
t.Fatalf("Failed to read test archive: %v", err)
}
server := createTestServer(t, map[string]string{
"project.tar.gz": string(archiveContent),
})
defer server.Close()
tempDir := t.TempDir()
destDir := filepath.Join(tempDir, "project-lib")
t.Run("CorrectInternalDir", func(t *testing.T) {
err := checkDownloadAndExtractLib(server.URL+"/project.tar.gz", destDir, "project-1.0.0")
if err != nil {
t.Fatalf("Failed to download and extract lib: %v", err)
}
for name, expectedContent := range files {
relPath := strings.TrimPrefix(name, "project-1.0.0/")
filePath := filepath.Join(destDir, relPath)
content, err := os.ReadFile(filePath)
if err != nil {
t.Errorf("Failed to read extracted file %s: %v", relPath, err)
continue
}
if string(content) != expectedContent {
t.Errorf("File %s: expected content %q, got %q", relPath, expectedContent, string(content))
}
}
})
t.Run("IncorrectInternalDir", func(t *testing.T) {
os.RemoveAll(destDir)
err := checkDownloadAndExtractLib(server.URL+"/project.tar.gz", destDir, "wrong-dir")
if err == nil {
t.Error("Expected error for missing internal dir, got nil")
}
})
}
// Mock test for WASI SDK (without actual download)
func TestWasiSDKExtractionLogic(t *testing.T) {
tempDir := t.TempDir()
@@ -490,3 +649,130 @@ func TestESPClangDownloadWhenNotExists(t *testing.T) {
}
}
}
func TestExtractZip(t *testing.T) {
// Create temporary test directory
tempDir := t.TempDir()
zipPath := filepath.Join(tempDir, "test.zip")
destDir := filepath.Join(tempDir, "extracted")
// 1. Test successful extraction
t.Run("SuccessfulExtraction", func(t *testing.T) {
// Create test ZIP file
if err := createTestZip(zipPath); err != nil {
t.Fatalf("Failed to create test zip: %v", err)
}
// Execute extraction
if err := extractZip(zipPath, destDir); err != nil {
t.Fatalf("extractZip failed: %v", err)
}
// Verify extraction results
verifyExtraction(t, destDir)
})
// 2. Test invalid ZIP file
t.Run("InvalidZipFile", func(t *testing.T) {
// Create invalid ZIP file (actually a text file)
if err := os.WriteFile(zipPath, []byte("not a zip file"), 0644); err != nil {
t.Fatal(err)
}
// Execute extraction and expect error
if err := extractZip(zipPath, destDir); err == nil {
t.Error("Expected error for invalid zip file, got nil")
}
})
// 3. Test non-writable destination
t.Run("UnwritableDestination", func(t *testing.T) {
// Create test ZIP file
if err := createTestZip(zipPath); err != nil {
t.Fatal(err)
}
// Create read-only destination directory
readOnlyDir := filepath.Join(tempDir, "readonly")
if err := os.MkdirAll(readOnlyDir, 0400); err != nil {
t.Fatal(err)
}
// Execute extraction and expect error
if err := extractZip(zipPath, readOnlyDir); err == nil {
t.Error("Expected error for unwritable destination, got nil")
}
})
}
// Create test ZIP file
func createTestZip(zipPath string) error {
// Create ZIP file
zipFile, err := os.Create(zipPath)
if err != nil {
return err
}
defer zipFile.Close()
zipWriter := zip.NewWriter(zipFile)
defer zipWriter.Close()
// Add directory
dirHeader := &zip.FileHeader{
Name: "testdir/",
Method: zip.Deflate,
Modified: time.Now(),
}
dirHeader.SetMode(os.ModeDir | 0755)
if _, err := zipWriter.CreateHeader(dirHeader); err != nil {
return err
}
// Add file1
file1, err := zipWriter.Create("file1.txt")
if err != nil {
return err
}
if _, err := file1.Write([]byte("Hello from file1")); err != nil {
return err
}
// Add nested file
nestedFile, err := zipWriter.Create("testdir/nested.txt")
if err != nil {
return err
}
if _, err := nestedFile.Write([]byte("Nested content")); err != nil {
return err
}
return nil
}
// Verify extraction results
func verifyExtraction(t *testing.T, destDir string) {
// Verify directory exists
if _, err := os.Stat(filepath.Join(destDir, "testdir")); err != nil {
t.Errorf("Directory not extracted: %v", err)
}
// Verify file1 content
file1Path := filepath.Join(destDir, "file1.txt")
content, err := os.ReadFile(file1Path)
if err != nil {
t.Errorf("Failed to read file1: %v", err)
}
if string(content) != "Hello from file1" {
t.Errorf("File1 content mismatch. Got: %s", content)
}
// Verify nested file content
nestedPath := filepath.Join(destDir, "testdir", "nested.txt")
content, err = os.ReadFile(nestedPath)
if err != nil {
t.Errorf("Failed to read nested file: %v", err)
}
if string(content) != "Nested content" {
t.Errorf("Nested file content mismatch. Got: %s", content)
}
}

View File

@@ -0,0 +1,42 @@
package crosscompile
import (
"fmt"
"path/filepath"
"github.com/goplus/llgo/internal/crosscompile/compile"
"github.com/goplus/llgo/internal/crosscompile/compile/libc"
"github.com/goplus/llgo/internal/crosscompile/compile/rtlib"
)
// GetCompileConfigByName retrieves libc compilation configuration by name
// Returns compilation file lists and corresponding cflags
func getLibcCompileConfigByName(baseDir, libcName, target, mcpu string) (*compile.CompileConfig, error) {
if libcName == "" {
return nil, fmt.Errorf("libc name cannot be empty")
}
libcDir := filepath.Join(baseDir, libcName)
switch libcName {
case "picolibc":
return libc.GetPicolibcConfig(libcDir, target), nil
case "newlib-esp32":
return libc.GetNewlibESP32Config(libcDir, target, mcpu), nil
default:
return nil, fmt.Errorf("unsupported libc: %s", libcName)
}
}
func getRTCompileConfigByName(baseDir, rtName, target string) (*compile.CompileConfig, error) {
if rtName == "" {
return nil, fmt.Errorf("rt name cannot be empty")
}
rtDir := filepath.Join(baseDir, rtName)
switch rtName {
case "compiler-rt":
return rtlib.GetCompilerRTConfig(rtDir, target), nil
default:
return nil, fmt.Errorf("unsupported rt: %s", rtName)
}
}

View File

@@ -0,0 +1,109 @@
//go:build !llgo
package crosscompile
import (
"path/filepath"
"slices"
"testing"
)
func TestGetLibcCompileConfigByName(t *testing.T) {
baseDir := "/test/base"
target := "armv7"
mcpu := "cortex-m4"
t.Run("EmptyName", func(t *testing.T) {
_, err := getLibcCompileConfigByName(baseDir, "", target, mcpu)
if err == nil || err.Error() != "libc name cannot be empty" {
t.Errorf("Expected empty name error, got: %v", err)
}
})
t.Run("UnsupportedLibc", func(t *testing.T) {
_, err := getLibcCompileConfigByName(baseDir, "invalid", target, mcpu)
if err == nil || err.Error() != "unsupported libc: invalid" {
t.Errorf("Expected unsupported libc error, got: %v", err)
}
})
t.Run("Picolibc", func(t *testing.T) {
cfg, err := getLibcCompileConfigByName(baseDir, "picolibc", target, mcpu)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if len(cfg.Groups) != 1 {
t.Fatalf("Expected 1 group, got %d", len(cfg.Groups))
}
group := cfg.Groups[0]
expectedFile := filepath.Join(baseDir, "picolibc", "newlib", "libc", "string", "memmem.c")
if !slices.Contains(group.Files, expectedFile) {
t.Errorf("Expected files [%s], got: %v", expectedFile, group.Files)
}
expectedFlag := "-I" + filepath.Join("/test", "base", "picolibc")
if !slices.Contains(group.CFlags, expectedFlag) {
t.Errorf("Expected flags [%s], got: %v", expectedFlag, group.CFlags)
}
})
t.Run("NewlibESP32", func(t *testing.T) {
cfg, err := getLibcCompileConfigByName(baseDir, "newlib-esp32", target, mcpu)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if len(cfg.Groups) != 3 {
t.Fatalf("Expected 3 group, got %d", len(cfg.Groups))
}
group := cfg.Groups[0]
expectedFile := filepath.Join(baseDir, "newlib-esp32", "libgloss", "xtensa", "crt1-boards.S")
if !slices.Contains(group.Files, expectedFile) {
t.Errorf("Expected files [%s], got: %v", expectedFile, group.Files)
}
expectedFlags := "-I" + filepath.Join(baseDir, "newlib-esp32", "libgloss")
if !slices.Contains(group.CFlags, expectedFlags) {
t.Errorf("Expected flags %v, got: %v", expectedFlags, group.CFlags)
}
})
}
func TestGetRTCompileConfigByName(t *testing.T) {
baseDir := "/test/base"
target := "wasm32"
t.Run("EmptyName", func(t *testing.T) {
_, err := getRTCompileConfigByName(baseDir, "", target)
if err == nil || err.Error() != "rt name cannot be empty" {
t.Errorf("Expected empty name error, got: %v", err)
}
})
t.Run("UnsupportedRT", func(t *testing.T) {
_, err := getRTCompileConfigByName(baseDir, "invalid", target)
if err == nil || err.Error() != "unsupported rt: invalid" {
t.Errorf("Expected unsupported rt error, got: %v", err)
}
})
t.Run("CompilerRT", func(t *testing.T) {
cfg, err := getRTCompileConfigByName(baseDir, "compiler-rt", target)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if len(cfg.Groups) != 1 {
t.Fatalf("Expected 1 group, got %d", len(cfg.Groups))
}
group := cfg.Groups[0]
expectedFile := filepath.Join(baseDir, "compiler-rt", "lib", "builtins", "absvdi2.c")
if !slices.Contains(group.Files, expectedFile) {
t.Errorf("Expected files [%s], got: %v", expectedFile, group.Files)
}
})
}

View File

@@ -16,6 +16,8 @@ type Config struct {
GOARCH string `json:"goarch"`
// Compiler and linker configuration
Libc string `json:"libc"`
RTLib string `json:"rtlib"`
Linker string `json:"linker"`
LinkerScript string `json:"linkerscript"`
CFlags []string `json:"cflags"`

View File

@@ -134,6 +134,12 @@ func (l *Loader) mergeConfig(dst, src *Config) {
if src.GOARCH != "" {
dst.GOARCH = src.GOARCH
}
if src.Libc != "" {
dst.Libc = src.Libc
}
if src.RTLib != "" {
dst.RTLib = src.RTLib
}
if src.Linker != "" {
dst.Linker = src.Linker
}

View File

@@ -0,0 +1,195 @@
__stack = ORIGIN(dram_seg) + LENGTH(dram_seg);
__MIN_STACK_SIZE = 0x1000;
_stack_top = __stack;
/* Default entry point */
ENTRY(_start)
SECTIONS
{
.text :
{
_iram_start = .;
/* Place the _start function at the beginning of IRAM.
* Just in case some versions of QEMU ignore the entry address when loading the ELF file. */
KEEP(*(.text._start))
KEEP (*(SORT_NONE(.init)))
*(.text .stub .text.* .gnu.linkonce.t.*)
/* .gnu.warning sections are handled specially by elf32.em. */
*(.gnu.warning)
KEEP (*(SORT_NONE(.fini)))
PROVIDE (__etext = .);
PROVIDE (_etext = .);
PROVIDE (etext = .);
} > iram_seg
.rodata :
{
*(.rodata .rodata.* .gnu.linkonce.r.*)
*(.rodata1)
*(.sdata2 .sdata2.* .gnu.linkonce.s2.*)
*(.sbss2 .sbss2.* .gnu.linkonce.sb2.*)
. = ALIGN(4);
__cpu_frequency = .;
LONG(CPU_FREQUENCY);
__uart0_clkdiv_reg = .;
LONG(UART0_CLKDIV_REG);
__uart0_clkdiv_val = .;
LONG(UART0_CLKDIV_VAL);
__uart0_tx_addr = .;
LONG(UART0_TX_ADDR);
__uart0_status = .;
LONG(UART0_STATUS);
} > iram_seg
.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array))
PROVIDE_HIDDEN (__preinit_array_end = .);
} > iram_seg
.init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
PROVIDE_HIDDEN (__init_array_end = .);
} > iram_seg
.fini_array :
{
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)))
KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors))
PROVIDE_HIDDEN (__fini_array_end = .);
} > iram_seg
.ctors :
{
/* gcc uses crtbegin.o to find the start of
the constructors, so we make sure it is
first. Because this is a wildcard, it
doesn't matter if the user does not
actually link against crtbegin.o; the
linker won't look for a file to match a
wildcard. The wildcard also means that it
doesn't matter which directory crtbegin.o
is in. */
KEEP (*crtbegin.o(.ctors))
KEEP (*crtbegin?.o(.ctors))
/* We don't want to include the .ctor section from
the crtend.o file until after the sorted ctors.
The .ctor section from the crtend file contains the
end of ctors marker and it must be last */
KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*(.ctors))
} > iram_seg
.dtors :
{
KEEP (*crtbegin.o(.dtors))
KEEP (*crtbegin?.o(.dtors))
KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
_iram_end = .;
} > iram_seg
/**
* This section is required to skip .iram0.text area because iram0_0_seg and
* dram0_0_seg reflect the same address space on different buses.
*/
.dram0.dummy (NOLOAD):
{
/* Add a gap only in case we have separate iram/dram regions */
. += ORIGIN(iram_seg) == ORIGIN(dram_seg) ? 0 : _iram_end - _iram_start;
} > dram_seg
.data :
{
_data_start = .;
*(.data .data.* .gnu.linkonce.d.*)
SORT(CONSTRUCTORS)
} > dram_seg
.data1 : { *(.data1) } > dram_seg
/* We want the small data sections together, so single-instruction offsets
can access them all, and initialized data all before uninitialized, so
we can shorten the on-disk segment size. */
.sdata :
{
PROVIDE(__global_pointer$ = . + 0x800);
*(.srodata.cst16) *(.srodata.cst8) *(.srodata.cst4) *(.srodata.cst2) *(.srodata .srodata.*)
*(.sdata .sdata.* .gnu.linkonce.s.*)
_edata = .; PROVIDE (edata = .);
. = .;
} > dram_seg
.eh_frame_hdr : { *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) } > dram_seg
.eh_frame : { KEEP (*(.eh_frame)) *(.eh_frame.*) } > dram_seg
.bss (NOLOAD) :
{
__bss_start = .;
*(.dynsbss)
*(.sbss .sbss.* .gnu.linkonce.sb.*)
*(.scommon)
*(.dynbss)
*(.bss .bss.* .gnu.linkonce.b.*)
*(COMMON)
/* Align here to ensure that the .bss section occupies space up to
_end. Align after .bss to ensure correct alignment even if the
.bss section disappears because there are no input sections.
FIXME: Why do we need it? When there is no .bss section, we do not
pad the .data section. */
. = ALIGN(. != 0 ? 32 / 8 : 1);
. = ALIGN(32 / 8);
. = ALIGN(32 / 8);
_end = .; PROVIDE (end = .);
} > dram_seg
/* Check if data + heap + stack exceeds RAM limit */
ASSERT(_end <= __stack - __MIN_STACK_SIZE, "region DRAM overflowed by .data and .bss sections")
/* Stabs debugging sections. */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
.gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) }
/* DWARF debug sections.
Symbols in the DWARF debugging sections are relative to the beginning
of the section so we begin them at 0. */
/* DWARF 1 */
.debug 0 : { *(.debug) }
.line 0 : { *(.line) }
/* GNU DWARF 1 extensions */
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
/* DWARF 1.1 and DWARF 2 */
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
/* DWARF 2 */
.debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end) }
.debug_frame 0 : { *(.debug_frame) }
.debug_str 0 : { *(.debug_str) }
.debug_loc 0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
/* SGI/MIPS DWARF 2 extensions */
.debug_weaknames 0 : { *(.debug_weaknames) }
.debug_funcnames 0 : { *(.debug_funcnames) }
.debug_typenames 0 : { *(.debug_typenames) }
.debug_varnames 0 : { *(.debug_varnames) }
/* DWARF 3 */
.debug_pubtypes 0 : { *(.debug_pubtypes) }
.debug_ranges 0 : { *(.debug_ranges) }
/* DWARF Extension. */
.debug_macro 0 : { *(.debug_macro) }
.debug_addr 0 : { *(.debug_addr) }
.gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
/DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
}

185
targets/esp32.app.elf.ld Executable file
View File

@@ -0,0 +1,185 @@
__stack = ORIGIN(dram_seg) + LENGTH(dram_seg);
__MIN_STACK_SIZE = 0x2000;
ENTRY(_start)
SECTIONS
{
. = SEGMENT_START("iram_seg", 0);
.vectors :
{
_vector_table = ABSOLUTE(.);
KEEP(*(.WindowVectors.text));
KEEP(*(.Level2InterruptVector.text));
KEEP(*(.Level3InterruptVector.text));
KEEP(*(.Level4InterruptVector.text));
KEEP(*(.Level5InterruptVector.text));
KEEP(*(.DebugExceptionVector.text));
KEEP(*(.NMIExceptionVector.text));
KEEP(*(.KernelExceptionVector.text));
KEEP(*(.UserExceptionVector.text));
KEEP(*(.DoubleExceptionVector.text));
KEEP(*(.ResetVector.text));
*(.*Vector.literal)
. = ALIGN (16);
} > iram_seg
text :
{
KEEP (*(.init.literal))
KEEP (*(SORT_NONE(.init)))
*(.literal .text .stub .literal.* .text.* .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.*)
/* .gnu.warning sections are handled specially by elf32.em. */
*(.gnu.warning)
KEEP (*(.fini.literal))
KEEP (*(SORT_NONE(.fini)))
} > iram_seg
PROVIDE (__etext = .);
PROVIDE (_etext = .);
PROVIDE (etext = .);
/* Adjust the address for the data segment. We want to adjust up to
the same address within the page on the next page up. */
. = ALIGN (CONSTANT (MAXPAGESIZE)) - ((CONSTANT (MAXPAGESIZE) - .) & (CONSTANT (MAXPAGESIZE) - 1)); . = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE));
.rodata :
{
*(.rodata .rodata.* .gnu.linkonce.r.*)
*(.rodata1)
*(.sdata2 .sdata2.* .gnu.linkonce.s2.*)
*(.sbss2 .sbss2.* .gnu.linkonce.sb2.*)
}
.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array))
PROVIDE_HIDDEN (__preinit_array_end = .);
}
.init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
PROVIDE_HIDDEN (__init_array_end = .);
}
.fini_array :
{
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)))
KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors))
PROVIDE_HIDDEN (__fini_array_end = .);
}
.ctors :
{
/* gcc uses crtbegin.o to find the start of
the constructors, so we make sure it is
first. Because this is a wildcard, it
doesn't matter if the user does not
actually link against crtbegin.o; the
linker won't look for a file to match a
wildcard. The wildcard also means that it
doesn't matter which directory crtbegin.o
is in. */
KEEP (*crtbegin.o(.ctors))
KEEP (*crtbegin?.o(.ctors))
/* We don't want to include the .ctor section from
the crtend.o file until after the sorted ctors.
The .ctor section from the crtend file contains the
end of ctors marker and it must be last */
KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*(.ctors))
}
.dtors :
{
KEEP (*crtbegin.o(.dtors))
KEEP (*crtbegin?.o(.dtors))
KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
}
_data_start = .;
.data :
{
*(.data .data.* .gnu.linkonce.d.*)
SORT(CONSTRUCTORS)
*(.data1)
}
_edata = .; PROVIDE (edata = .);
. = .;
__bss_start = .;
.bss :
{
*(.dynsbss)
*(.sbss .sbss.* .gnu.linkonce.sb.*)
*(.scommon)
*(.dynbss)
*(.bss .bss.* .gnu.linkonce.b.*)
*(COMMON)
/* Align here to ensure that the .bss section occupies space up to
_end. Align after .bss to ensure correct alignment even if the
.bss section disappears because there are no input sections.
FIXME: Why do we need it? When there is no .bss section, we do not
pad the .data section. */
. = ALIGN(. != 0 ? 32 / 8 : 1);
}
. = ALIGN(32 / 8);
. = ALIGN(32 / 8);
_end = .; PROVIDE (end = .);
. = DATA_SEGMENT_END (.);
/* Check if data + heap + stack exceeds RAM limit */
ASSERT(. <= __stack - __MIN_STACK_SIZE, "region DRAM overflowed by .data and .bss sections")
/* Stabs debugging sections. */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
.gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) }
/* DWARF debug sections.
Symbols in the DWARF debugging sections are relative to the beginning
of the section so we begin them at 0. */
/* DWARF 1 */
.debug 0 : { *(.debug) }
.line 0 : { *(.line) }
/* GNU DWARF 1 extensions */
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
/* DWARF 1.1 and DWARF 2 */
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
/* DWARF 2 */
.debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end) }
.debug_frame 0 : { *(.debug_frame) }
.debug_str 0 : { *(.debug_str) }
.debug_loc 0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
/* SGI/MIPS DWARF 2 extensions */
.debug_weaknames 0 : { *(.debug_weaknames) }
.debug_funcnames 0 : { *(.debug_funcnames) }
.debug_typenames 0 : { *(.debug_typenames) }
.debug_varnames 0 : { *(.debug_varnames) }
/* DWARF 3 */
.debug_pubtypes 0 : { *(.debug_pubtypes) }
.debug_ranges 0 : { *(.debug_ranges) }
/* DWARF Extension. */
.debug_macro 0 : { *(.debug_macro) }
.debug_addr 0 : { *(.debug_addr) }
.gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
/DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
}
_sbss = __bss_start;
_ebss = _end;

View File

@@ -1,20 +1,25 @@
{
"inherits": ["xtensa"],
"inherits": [
"xtensa"
],
"cpu": "esp32",
"features": "+atomctl,+bool,+clamps,+coprocessor,+debug,+density,+dfpaccel,+div32,+exception,+fp,+highpriinterrupts,+interrupt,+loop,+mac16,+memctl,+minmax,+miscsr,+mul32,+mul32high,+nsa,+prid,+regprotect,+rvector,+s32c1i,+sext,+threadptr,+timerint,+windowed",
"build-tags": ["esp32", "esp"],
"build-tags": [
"esp32",
"esp"
],
"scheduler": "tasks",
"serial": "uart",
"linker": "ld.lld",
"default-stack-size": 2048,
"rtlib": "compiler-rt",
"libc": "picolibc",
"linkerscript": "targets/esp32.ld",
"extra-files": [
"targets/device/esp/esp32.S"
],
"libc": "newlib-esp32",
"linkerscript": "targets/esp32.memory.elf.ld",
"extra-files": [],
"binary-format": "esp32",
"flash-command": "esptool.py --chip=esp32 --port {port} write_flash 0x1000 {bin} -ff 80m -fm dout",
"emulator": "qemu-system-xtensa -machine esp32 -nographic -drive file={img},if=mtd,format=raw",
"gdb": ["xtensa-esp32-elf-gdb"]
"gdb": [
"xtensa-esp32-elf-gdb"
]
}

View File

@@ -1,202 +0,0 @@
/* Linker script for the ESP32 */
MEMORY
{
/* Data RAM. Allows byte access.
* There are various data RAM regions:
* SRAM2: 0x3FFA_E000..0x3FFD_FFFF (72 + 128 = 200K)
* SRAM1: 0x3FFE_0000..0x3FFF_FFFF (128K)
* This gives us 328K of contiguous RAM, which is the largest span possible.
* SRAM1 has other addresses as well but the datasheet seems to indicate
* these are aliases.
*/
DRAM (rw) : ORIGIN = 0x3FFAE000, LENGTH = 200K + 128K /* Internal SRAM 1 + 2 */
/* Instruction RAM. */
IRAM (x) : ORIGIN = 0x40080000, LENGTH = 128K /* Internal SRAM 0 */
}
/* The entry point. It is set in the image flashed to the chip, so must be
* defined.
*/
ENTRY(call_start_cpu0)
SECTIONS
{
/* Constant literals and code. Loaded into IRAM for now. Eventually, most
* code should be executed directly from flash.
* Note that literals must be before code for the l32r instruction to work.
*/
.text : ALIGN(4)
{
*(.literal.call_start_cpu0)
*(.text.call_start_cpu0)
*(.literal .text)
*(.literal.* .text.*)
} >IRAM
/* Put the stack at the bottom of DRAM, so that the application will
* crash on stack overflow instead of silently corrupting memory.
* See: http://blog.japaric.io/stack-overflow-protection/ */
.stack (NOLOAD) :
{
. = ALIGN(16);
. += _stack_size;
_stack_top = .;
} >DRAM
/* Constant global variables.
* They are loaded in DRAM for ease of use. Eventually they should be stored
* in flash and loaded directly from there but they're kept in RAM to make
* sure they can always be accessed (even in interrupts).
*/
.rodata : ALIGN(4)
{
*(.rodata)
*(.rodata.*)
} >DRAM
/* Mutable global variables.
*/
.data : ALIGN(4)
{
_sdata = ABSOLUTE(.);
*(.data)
*(.data.*)
_edata = ABSOLUTE(.);
} >DRAM
/* Check that the boot ROM stack (for the APP CPU) does not overlap with the
* data that is loaded by the boot ROM. There may be ways to avoid this
* issue if it occurs in practice.
* The magic value here is _stack_sentry in the boot ROM ELF file.
*/
ASSERT(_edata < 0x3ffe1320, "the .data section overlaps with the stack used by the boot ROM, possibly causing corruption at startup")
/* Global variables that are mutable and zero-initialized.
* These must be zeroed at startup (unlike data, which is loaded by the
* bootloader).
*/
.bss (NOLOAD) : ALIGN(4)
{
. = ALIGN (4);
_sbss = ABSOLUTE(.);
*(.bss)
*(.bss.*)
. = ALIGN (4);
_ebss = ABSOLUTE(.);
} >DRAM
}
/* For the garbage collector.
*/
_globals_start = _sdata;
_globals_end = _ebss;
_heap_start = _ebss;
_heap_end = ORIGIN(DRAM) + LENGTH(DRAM);
_stack_size = 4K;
/* From ESP-IDF:
* components/esp_rom/esp32/ld/esp32.rom.newlib-funcs.ld
* This is the subset that is sometimes used by LLVM during codegen, and thus
* must always be present.
*/
memcpy = 0x4000c2c8;
memmove = 0x4000c3c0;
memset = 0x4000c44c;
/* From ESP-IDF:
* components/esp_rom/esp32/ld/esp32.rom.libgcc.ld
* These are called from LLVM during codegen. The original license is Apache
* 2.0, but I believe that a list of function names and addresses can't really
* be copyrighted.
*/
__absvdi2 = 0x4006387c;
__absvsi2 = 0x40063868;
__adddf3 = 0x40002590;
__addsf3 = 0x400020e8;
__addvdi3 = 0x40002cbc;
__addvsi3 = 0x40002c98;
__ashldi3 = 0x4000c818;
__ashrdi3 = 0x4000c830;
__bswapdi2 = 0x40064b08;
__bswapsi2 = 0x40064ae0;
__clrsbdi2 = 0x40064b7c;
__clrsbsi2 = 0x40064b64;
__clzdi2 = 0x4000ca50;
__clzsi2 = 0x4000c7e8;
__cmpdi2 = 0x40063820;
__ctzdi2 = 0x4000ca64;
__ctzsi2 = 0x4000c7f0;
__divdc3 = 0x400645a4;
__divdf3 = 0x40002954;
__divdi3 = 0x4000ca84;
__divsi3 = 0x4000c7b8;
__eqdf2 = 0x400636a8;
__eqsf2 = 0x40063374;
__extendsfdf2 = 0x40002c34;
__ffsdi2 = 0x4000ca2c;
__ffssi2 = 0x4000c804;
__fixdfdi = 0x40002ac4;
__fixdfsi = 0x40002a78;
__fixsfdi = 0x4000244c;
__fixsfsi = 0x4000240c;
__fixunsdfsi = 0x40002b30;
__fixunssfdi = 0x40002504;
__fixunssfsi = 0x400024ac;
__floatdidf = 0x4000c988;
__floatdisf = 0x4000c8c0;
__floatsidf = 0x4000c944;
__floatsisf = 0x4000c870;
__floatundidf = 0x4000c978;
__floatundisf = 0x4000c8b0;
__floatunsidf = 0x4000c938;
__floatunsisf = 0x4000c864;
__gcc_bcmp = 0x40064a70;
__gedf2 = 0x40063768;
__gesf2 = 0x4006340c;
__gtdf2 = 0x400636dc;
__gtsf2 = 0x400633a0;
__ledf2 = 0x40063704;
__lesf2 = 0x400633c0;
__lshrdi3 = 0x4000c84c;
__ltdf2 = 0x40063790;
__ltsf2 = 0x4006342c;
__moddi3 = 0x4000cd4c;
__modsi3 = 0x4000c7c0;
__muldc3 = 0x40063c90;
__muldf3 = 0x4006358c;
__muldi3 = 0x4000c9fc;
__mulsf3 = 0x400632c8;
__mulsi3 = 0x4000c7b0;
__mulvdi3 = 0x40002d78;
__mulvsi3 = 0x40002d60;
__nedf2 = 0x400636a8;
__negdf2 = 0x400634a0;
__negdi2 = 0x4000ca14;
__negsf2 = 0x400020c0;
__negvdi2 = 0x40002e98;
__negvsi2 = 0x40002e78;
__nesf2 = 0x40063374;
__nsau_data = 0x3ff96544;
__paritysi2 = 0x40002f3c;
__popcount_tab = 0x3ff96544;
__popcountdi2 = 0x40002ef8;
__popcountsi2 = 0x40002ed0;
__powidf2 = 0x400638e4;
__subdf3 = 0x400026e4;
__subsf3 = 0x400021d0;
__subvdi3 = 0x40002d20;
__subvsi3 = 0x40002cf8;
__truncdfsf2 = 0x40002b90;
__ucmpdi2 = 0x40063840;
__udiv_w_sdiv = 0x40064bec;
__udivdi3 = 0x4000cff8;
__udivmoddi4 = 0x40064bf4;
__udivsi3 = 0x4000c7c8;
__umoddi3 = 0x4000d280;
__umodsi3 = 0x4000c7d0;
__umulsidi3 = 0x4000c7d8;
__unorddf2 = 0x400637f4;
__unordsf2 = 0x40063478;

26
targets/esp32.memory.elf.ld Executable file
View File

@@ -0,0 +1,26 @@
/*
* IROM/DRAM definition in QEMU:
* [ESP32_MEMREGION_IROM] = { 0x40000000, 0x70000 },
* [ESP32_MEMREGION_DRAM] = { 0x3ffae000, 0x52000 },
*
* In theory we could use whole DRAM section, but I had some faults when using
* memory in range 0x3ffae000 - 0x3ffb0000
*
* But used memory range for data such as esp-idf for ESP32 to satisfy user's
* expectation on chip emulation
*
* Pass '--defsym=entire_dram_seg=1' to linker script to use whole DRAM
*
*/
MEMORY
{
iram_seg (X) : org = 0x40078000, len = 0x28000
/* 64k at the end of DRAM, after ROM bootloader stack
* or entire DRAM (for QEMU only)
*/
dram_seg (RW) : org = 0x3FFF0000 ,
len = 0x10000
}
INCLUDE "targets/esp32.app.elf.ld";

33
targets/esp32c2.memory.ld Normal file
View File

@@ -0,0 +1,33 @@
# See "System Structure and Address Mapping" figure at
# https://www.espressif.com/sites/default/files/documentation/esp8684_technical_reference_manual_en.pdf
ICACHE_SIZE = 0x4000;
# Skip possible ICACHE area
IRAM_START_ADDRESS = 0x4037C000 + ICACHE_SIZE;
IRAM_LEN = 0x40000 - ICACHE_SIZE;
DRAM_START_ADDRESS = 0x3FCA0000;
DRAM_LEN = 0x40000;
# Docs say that:
# The default console baud rate on ESP32-C2:
# - 115200 when a 40 MHz XTAL is used
# - 74880 when a 26 MHz XTAL is used
#
# It seems something wrong with CPU_FREQUENCY and UART0_BAUD definitions,
# but UART0_CLKDIV_VAL == 173 gives expected baud rate 74880 on 26 MHz XTAL.
CPU_FREQUENCY = 20000000;
UART0_BAUD = 115200;
UART0_CLKDIV_REG = 0x60000014;
UART0_CLKDIV_VAL = CPU_FREQUENCY / UART0_BAUD;
UART0_STATUS = 0x6000001C;
UART0_TX_ADDR = 0x60000000;
MEMORY
{
iram_seg (RX) : org = IRAM_START_ADDRESS, len = IRAM_LEN
dram_seg (RW) : org = DRAM_START_ADDRESS, len = DRAM_LEN
}
INCLUDE "targets/esp32-riscv.app.elf.ld";

View File

@@ -1,23 +1,31 @@
{
"inherits": ["riscv32"],
"inherits": [
"riscv32"
],
"features": "+32bit,+c,+m,+zmmul,-a,-b,-d,-e,-experimental-smmpm,-experimental-smnpm,-experimental-ssnpm,-experimental-sspm,-experimental-ssqosid,-experimental-supm,-experimental-zacas,-experimental-zalasr,-experimental-zicfilp,-experimental-zicfiss,-f,-h,-relax,-shcounterenw,-shgatpa,-shtvala,-shvsatpa,-shvstvala,-shvstvecd,-smaia,-smcdeleg,-smcsrind,-smepmp,-smstateen,-ssaia,-ssccfg,-ssccptr,-sscofpmf,-sscounterenw,-sscsrind,-ssstateen,-ssstrict,-sstc,-sstvala,-sstvecd,-ssu64xl,-svade,-svadu,-svbare,-svinval,-svnapot,-svpbmt,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xesppie,-xsfcease,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xsifivecdiscarddlone,-xsifivecflushdlone,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-xwchc,-za128rs,-za64rs,-zaamo,-zabha,-zalrsc,-zama16b,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmop,-zcmp,-zcmt,-zdinx,-zfa,-zfbfmin,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zicsr,-zifencei,-zihintntl,-zihintpause,-zihpm,-zimop,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-ztso,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfbfmin,-zvfbfwma,-zvfh,-zvfhmin,-zvkb,-zvkg,-zvkn,-zvknc,-zvkned,-zvkng,-zvknha,-zvknhb,-zvks,-zvksc,-zvksed,-zvksg,-zvksh,-zvkt,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b",
"build-tags": ["esp32c3", "esp"],
"build-tags": [
"esp32c3",
"esp"
],
"serial": "usb",
"rtlib": "compiler-rt",
"libc": "picolibc",
"libc": "newlib-esp32",
"cflags": [
"-march=rv32imc"
],
"linkerscript": "targets/esp32c3.ld",
"extra-files": [
"targets/device/esp/esp32c3.S"
],
"linkerscript": "targets/esp32c3.memory.ld",
"extra-files": [],
"binary-format": "esp32c3",
"flash-command": "esptool.py --chip=esp32c3 --port {port} write_flash 0x0 {bin}",
"serial-port": ["303a:1001"],
"serial-port": [
"303a:1001"
],
"openocd-interface": "esp_usb_jtag",
"openocd-target": "esp32c3",
"openocd-commands": ["gdb_memory_map disable"],
"gdb": ["riscv32-esp-elf-gdb"]
"openocd-commands": [
"gdb_memory_map disable"
],
"gdb": [
"riscv32-esp-elf-gdb"
]
}

File diff suppressed because it is too large Load Diff

26
targets/esp32c3.memory.ld Normal file
View File

@@ -0,0 +1,26 @@
# See "System Structure and Address Mapping" figure at
# https://www.espressif.com/sites/default/files/documentation/esp32-c3_technical_reference_manual_en.pdf
ICACHE_SIZE = 0x4000;
# Skip possible ICACHE area
IRAM_START_ADDRESS = 0x4037C000 + ICACHE_SIZE;
IRAM_LEN = 0x64000 - ICACHE_SIZE;
DRAM_START_ADDRESS = 0x3FC80000;
DRAM_LEN = 0x40000;
CPU_FREQUENCY = 20000000;
UART0_BAUD = 115200;
UART0_CLKDIV_REG = 0x60000014;
UART0_CLKDIV_VAL = CPU_FREQUENCY / UART0_BAUD;
UART0_STATUS = 0x6000001C;
UART0_TX_ADDR = 0x60000000;
MEMORY
{
iram_seg (RX) : org = IRAM_START_ADDRESS, len = IRAM_LEN
dram_seg (RW) : org = DRAM_START_ADDRESS, len = DRAM_LEN
}
INCLUDE "targets/esp32-riscv.app.elf.ld";

22
targets/esp32c5.memory.ld Normal file
View File

@@ -0,0 +1,22 @@
# Memory layout obtained from IDF linker files.
SRAM_START_ADDRESS = 0x40800000;
SRAM_LEN = 0x60000;
CPU_FREQUENCY = 40000000;
UART0_BAUD = 115200;
UART0_CLKDIV_REG = 0x60000014;
UART0_CLKDIV_VAL = CPU_FREQUENCY / UART0_BAUD;
UART0_STATUS = 0x6000001C;
UART0_TX_ADDR = 0x60000000;
MEMORY
{
sram_seg (RWX) : org = SRAM_START_ADDRESS, len = SRAM_LEN
}
REGION_ALIAS("iram_seg", sram_seg);
REGION_ALIAS("dram_seg", sram_seg);
INCLUDE "targets/esp32-riscv.app.elf.ld";

22
targets/esp32c6.memory.ld Normal file
View File

@@ -0,0 +1,22 @@
# See "System Structure and Address Mapping" figure at
# https://www.espressif.com/sites/default/files/documentation/esp32-c6_technical_reference_manual_en.pdf
SRAM_START_ADDRESS = 0x40800000;
SRAM_LEN = 0x80000;
CPU_FREQUENCY = 40000000;
UART0_BAUD = 115200;
UART0_CLKDIV_REG = 0x60000014;
UART0_CLKDIV_VAL = CPU_FREQUENCY / UART0_BAUD;
UART0_STATUS = 0x6000001C;
UART0_TX_ADDR = 0x60000000;
MEMORY
{
sram_seg (RWX) : org = SRAM_START_ADDRESS, len = SRAM_LEN
}
REGION_ALIAS("iram_seg", sram_seg);
REGION_ALIAS("dram_seg", sram_seg);
INCLUDE "targets/esp32-riscv.app.elf.ld";

View File

@@ -0,0 +1,21 @@
# Memory layout obtained from IDF linker files.
SRAM_START_ADDRESS = 0x40800000;
SRAM_LEN = 0x50000;
CPU_FREQUENCY = 40000000;
UART0_BAUD = 115200;
UART0_CLKDIV_REG = 0x60000014;
UART0_CLKDIV_VAL = CPU_FREQUENCY / UART0_BAUD;
UART0_STATUS = 0x6000001C;
UART0_TX_ADDR = 0x60000000;
MEMORY
{
sram_seg (RWX) : org = SRAM_START_ADDRESS, len = SRAM_LEN
}
REGION_ALIAS("iram_seg", sram_seg);
REGION_ALIAS("dram_seg", sram_seg);
INCLUDE "targets/esp32-riscv.app.elf.ld";

22
targets/esp32h2.memory.ld Normal file
View File

@@ -0,0 +1,22 @@
# See "System Structure and Address Mapping" figure at
# https://www.espressif.com/sites/default/files/documentation/esp32-c3_technical_reference_manual_en.pdf
SRAM_START_ADDRESS = 0x40800000;
SRAM_LEN = 0x50000;
CPU_FREQUENCY = 40000000;
UART0_BAUD = 115200;
UART0_CLKDIV_REG = 0x60000014;
UART0_CLKDIV_VAL = CPU_FREQUENCY / UART0_BAUD;
UART0_STATUS = 0x6000001C;
UART0_TX_ADDR = 0x60000000;
MEMORY
{
sram_seg (RWX) : org = SRAM_START_ADDRESS, len = SRAM_LEN
}
REGION_ALIAS("iram_seg", sram_seg);
REGION_ALIAS("dram_seg", sram_seg);
INCLUDE "targets/esp32-riscv.app.elf.ld";

View File

@@ -0,0 +1,21 @@
# Memory layout obtained from IDF linker files.
SRAM_START_ADDRESS = 0x40800000;
SRAM_LEN = 0x50000;
CPU_FREQUENCY = 40000000;
UART0_BAUD = 115200;
UART0_CLKDIV_REG = 0x60000014;
UART0_CLKDIV_VAL = CPU_FREQUENCY / UART0_BAUD;
UART0_STATUS = 0x6000001C;
UART0_TX_ADDR = 0x60000000;
MEMORY
{
sram_seg (RWX) : org = SRAM_START_ADDRESS, len = SRAM_LEN
}
REGION_ALIAS("iram_seg", sram_seg);
REGION_ALIAS("dram_seg", sram_seg);
INCLUDE "targets/esp32-riscv.app.elf.ld";

23
targets/esp32p4.memory.ld Normal file
View File

@@ -0,0 +1,23 @@
# Memory layout obtained from IDF linker files.
L2_CACHE_SIZE = 0x40000;
SRAM_START_ADDRESS = 0x4FF00000;
SRAM_LEN = 0xC0000 - L2_CACHE_SIZE;
CPU_FREQUENCY = 40000000;
UART0_BAUD = 115200;
UART0_CLKDIV_REG = 0x500CA014;
UART0_CLKDIV_VAL = CPU_FREQUENCY / UART0_BAUD;
UART0_STATUS = 0x500CA01C;
UART0_TX_ADDR = 0x500CA000;
MEMORY
{
sram_seg (RWX) : org = SRAM_START_ADDRESS, len = SRAM_LEN
}
REGION_ALIAS("iram_seg", sram_seg);
REGION_ALIAS("dram_seg", sram_seg);
INCLUDE "targets/esp32-riscv.app.elf.ld";

View File

@@ -2,15 +2,23 @@
"llvm-target": "xtensa",
"goos": "linux",
"goarch": "arm",
"build-tags": ["xtensa", "baremetal", "linux", "arm"],
"build-tags": [
"xtensa",
"baremetal",
"linux",
"arm"
],
"gc": "conservative",
"scheduler": "none",
"cflags": [
"-Werror",
"-fshort-enums",
"-Wno-macro-redefined",
"-fno-exceptions", "-fno-unwind-tables", "-fno-asynchronous-unwind-tables",
"-ffunction-sections", "-fdata-sections"
"-fno-exceptions",
"-fno-unwind-tables",
"-fno-asynchronous-unwind-tables",
"-ffunction-sections",
"-fdata-sections"
],
"ldflags": [
"--gc-sections"