Merge commit '6588f36123eababf6e24564b49e5af374285d2b5' into optional-esp-clang

# Conflicts:
#	internal/crosscompile/crosscompile.go
#	internal/crosscompile/crosscompile_test.go
This commit is contained in:
Li Jie
2025-09-03 09:32:38 +08:00
59 changed files with 5523 additions and 2542 deletions

View File

@@ -0,0 +1,21 @@
package main
import (
"fmt"
)
//llgo:link asmFull llgo.asm
func asmFull(instruction string, regs map[string]any) uintptr { return 0 }
var testVar = 0
func main() {
verify()
}
func check(expected, actual int) {
if expected != actual {
panic(fmt.Sprintf("Expected: %d, Got: %d\n", expected, actual))
}
fmt.Println("asm check passed:", actual)
}

View File

@@ -0,0 +1,31 @@
//go:build darwin && arm64
package main
import "unsafe"
func verify() {
// 0 output & 0 input
asmFull("nop", nil)
// 0 output & 1 input with memory address
addr := uintptr(unsafe.Pointer(&testVar))
asmFull("str {value}, [{addr}]", map[string]any{
"addr": addr,
"value": 43,
})
check(43, testVar)
// 1 output & 1 input
res1 := asmFull("mov {}, {value}", map[string]any{
"value": 41,
})
check(41, int(res1))
// 1 output & 2 inputs
res2 := asmFull("add {}, {a}, {b}", map[string]any{
"a": 25,
"b": 17,
})
check(42, int(res2))
}

View File

@@ -0,0 +1,30 @@
//go:build linux && amd64
package main
import "unsafe"
func verify() {
// 0 output & 0 input
asmFull("nop", nil)
// 0 output & 1 input with memory address
addr := uintptr(unsafe.Pointer(&testVar))
asmFull("movq {value}, ({addr})", map[string]any{
"addr": addr,
"value": 43,
})
check(43, testVar)
// 1 output & 1 input
res1 := asmFull("movq {value}, {}", map[string]any{
"value": 41,
})
check(41, int(res1))
res2 := asmFull("leaq ({a},{b}), {}", map[string]any{
"a": 25,
"b": 17,
})
check(42, int(res2))
}

View File

@@ -13,7 +13,17 @@ func main() {
if len(entries) == 0 {
panic("No files found")
}
var check int
for _, e := range entries {
fmt.Printf("%s isDir[%t]\n", e.Name(), e.IsDir())
if !e.IsDir() {
switch e.Name() {
case "go.sum", "go.mod":
check++
}
}
}
if check != 2 {
panic("Bad readdir entries go.mod/go.sum")
}
}

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)
}

27
cl/_testrt/asmfull/in.go Normal file
View File

@@ -0,0 +1,27 @@
package main
import _ "unsafe"
//go:linkname asmFull llgo.asm
func asmFull(instruction string, regs map[string]any) uintptr
func main() {
// no input,no return value
asmFull("nop", nil)
// input only,no return value
asmFull("# test value {value}", map[string]any{"value": 42})
// input with return value
res1 := asmFull("mov {}, {value}", map[string]any{
"value": 42,
})
println("Result:", res1)
// note(zzy): multiple inputs with return value
// only for test register & constraint,not have actual meaning
// the ir compare cannot crossplatform currently
// so just use a comment to test it
res2 := asmFull("# calc {x} + {y} -> {}", map[string]any{
"x": 25,
"y": 17,
})
println("Result:", res2)
}

203
cl/_testrt/asmfull/out.ll Normal file
View File

@@ -0,0 +1,203 @@
; ModuleID = 'github.com/goplus/llgo/cl/_testrt/asmfull'
source_filename = "github.com/goplus/llgo/cl/_testrt/asmfull"
%"github.com/goplus/llgo/runtime/internal/runtime.eface" = type { ptr, ptr }
%"github.com/goplus/llgo/runtime/internal/runtime.String" = type { ptr, i64 }
%"github.com/goplus/llgo/runtime/internal/runtime.Slice" = type { ptr, i64, i64 }
%"github.com/goplus/llgo/runtime/abi.StructField" = type { %"github.com/goplus/llgo/runtime/internal/runtime.String", ptr, i64, %"github.com/goplus/llgo/runtime/internal/runtime.String", i1 }
@"github.com/goplus/llgo/cl/_testrt/asmfull.init$guard" = global i1 false, align 1
@_llgo_string = linkonce global ptr null, align 8
@_llgo_any = linkonce global ptr null, align 8
@0 = private unnamed_addr constant [41 x i8] c"github.com/goplus/llgo/cl/_testrt/asmfull", align 1
@"map[_llgo_string]_llgo_any" = linkonce global ptr null, align 8
@1 = private unnamed_addr constant [7 x i8] c"topbits", align 1
@2 = private unnamed_addr constant [4 x i8] c"keys", align 1
@3 = private unnamed_addr constant [5 x i8] c"elems", align 1
@4 = private unnamed_addr constant [8 x i8] c"overflow", align 1
@_llgo_int = linkonce global ptr null, align 8
@5 = private unnamed_addr constant [5 x i8] c"value", align 1
@6 = private unnamed_addr constant [7 x i8] c"Result:", align 1
@7 = private unnamed_addr constant [1 x i8] c"x", align 1
@8 = private unnamed_addr constant [1 x i8] c"y", align 1
define void @"github.com/goplus/llgo/cl/_testrt/asmfull.init"() {
_llgo_0:
%0 = load i1, ptr @"github.com/goplus/llgo/cl/_testrt/asmfull.init$guard", align 1
br i1 %0, label %_llgo_2, label %_llgo_1
_llgo_1: ; preds = %_llgo_0
store i1 true, ptr @"github.com/goplus/llgo/cl/_testrt/asmfull.init$guard", align 1
call void @"github.com/goplus/llgo/cl/_testrt/asmfull.init$after"()
br label %_llgo_2
_llgo_2: ; preds = %_llgo_1, %_llgo_0
ret void
}
define void @"github.com/goplus/llgo/cl/_testrt/asmfull.main"() {
_llgo_0:
call void asm sideeffect "nop", ""()
%0 = load ptr, ptr @_llgo_string, align 8
%1 = load ptr, ptr @_llgo_any, align 8
%2 = load ptr, ptr @"map[_llgo_string]_llgo_any", align 8
%3 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.MakeMap"(ptr %2, i64 1)
%4 = load ptr, ptr @_llgo_int, align 8
%5 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.eface" undef, ptr %4, 0
%6 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.eface" %5, ptr inttoptr (i64 42 to ptr), 1
%7 = load ptr, ptr @"map[_llgo_string]_llgo_any", align 8
%8 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 16)
store %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @5, i64 5 }, ptr %8, align 8
%9 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.MapAssign"(ptr %7, ptr %3, ptr %8)
store %"github.com/goplus/llgo/runtime/internal/runtime.eface" %6, ptr %9, align 8
call void asm sideeffect "# test value ${0}", "r"(i64 42)
%10 = load ptr, ptr @"map[_llgo_string]_llgo_any", align 8
%11 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.MakeMap"(ptr %10, i64 1)
%12 = load ptr, ptr @_llgo_int, align 8
%13 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.eface" undef, ptr %12, 0
%14 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.eface" %13, ptr inttoptr (i64 42 to ptr), 1
%15 = load ptr, ptr @"map[_llgo_string]_llgo_any", align 8
%16 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 16)
store %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @5, i64 5 }, ptr %16, align 8
%17 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.MapAssign"(ptr %15, ptr %11, ptr %16)
store %"github.com/goplus/llgo/runtime/internal/runtime.eface" %14, ptr %17, align 8
%18 = call i64 asm sideeffect "mov $0, ${1}", "=&r,r"(i64 42)
call void @"github.com/goplus/llgo/runtime/internal/runtime.PrintString"(%"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @6, i64 7 })
call void @"github.com/goplus/llgo/runtime/internal/runtime.PrintByte"(i8 32)
call void @"github.com/goplus/llgo/runtime/internal/runtime.PrintUint"(i64 %18)
call void @"github.com/goplus/llgo/runtime/internal/runtime.PrintByte"(i8 10)
%19 = load ptr, ptr @"map[_llgo_string]_llgo_any", align 8
%20 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.MakeMap"(ptr %19, i64 2)
%21 = load ptr, ptr @_llgo_int, align 8
%22 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.eface" undef, ptr %21, 0
%23 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.eface" %22, ptr inttoptr (i64 25 to ptr), 1
%24 = load ptr, ptr @"map[_llgo_string]_llgo_any", align 8
%25 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 16)
store %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @7, i64 1 }, ptr %25, align 8
%26 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.MapAssign"(ptr %24, ptr %20, ptr %25)
store %"github.com/goplus/llgo/runtime/internal/runtime.eface" %23, ptr %26, align 8
%27 = load ptr, ptr @_llgo_int, align 8
%28 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.eface" undef, ptr %27, 0
%29 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.eface" %28, ptr inttoptr (i64 17 to ptr), 1
%30 = load ptr, ptr @"map[_llgo_string]_llgo_any", align 8
%31 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 16)
store %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @8, i64 1 }, ptr %31, align 8
%32 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.MapAssign"(ptr %30, ptr %20, ptr %31)
store %"github.com/goplus/llgo/runtime/internal/runtime.eface" %29, ptr %32, align 8
%33 = call i64 asm sideeffect "# calc ${1} + ${2} -> $0", "=&r,r,r"(i64 25, i64 17)
call void @"github.com/goplus/llgo/runtime/internal/runtime.PrintString"(%"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @6, i64 7 })
call void @"github.com/goplus/llgo/runtime/internal/runtime.PrintByte"(i8 32)
call void @"github.com/goplus/llgo/runtime/internal/runtime.PrintUint"(i64 %33)
call void @"github.com/goplus/llgo/runtime/internal/runtime.PrintByte"(i8 10)
ret void
}
define void @"github.com/goplus/llgo/cl/_testrt/asmfull.init$after"() {
_llgo_0:
%0 = load ptr, ptr @_llgo_string, align 8
%1 = icmp eq ptr %0, null
br i1 %1, label %_llgo_1, label %_llgo_2
_llgo_1: ; preds = %_llgo_0
%2 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Basic"(i64 24)
store ptr %2, ptr @_llgo_string, align 8
br label %_llgo_2
_llgo_2: ; preds = %_llgo_1, %_llgo_0
%3 = load ptr, ptr @_llgo_any, align 8
%4 = icmp eq ptr %3, null
br i1 %4, label %_llgo_3, label %_llgo_4
_llgo_3: ; preds = %_llgo_2
%5 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 0)
%6 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" undef, ptr %5, 0
%7 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %6, i64 0, 1
%8 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %7, i64 0, 2
%9 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Interface"(%"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @0, i64 41 }, %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %8)
store ptr %9, ptr @_llgo_any, align 8
br label %_llgo_4
_llgo_4: ; preds = %_llgo_3, %_llgo_2
%10 = load ptr, ptr @"map[_llgo_string]_llgo_any", align 8
%11 = icmp eq ptr %10, null
br i1 %11, label %_llgo_5, label %_llgo_6
_llgo_5: ; preds = %_llgo_4
%12 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Basic"(i64 24)
%13 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 0)
%14 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" undef, ptr %13, 0
%15 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %14, i64 0, 1
%16 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %15, i64 0, 2
%17 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Interface"(%"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @0, i64 41 }, %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %16)
%18 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Basic"(i64 40)
%19 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.ArrayOf"(i64 8, ptr %18)
%20 = call %"github.com/goplus/llgo/runtime/abi.StructField" @"github.com/goplus/llgo/runtime/internal/runtime.StructField"(%"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @1, i64 7 }, ptr %19, i64 0, %"github.com/goplus/llgo/runtime/internal/runtime.String" zeroinitializer, i1 false)
%21 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Basic"(i64 24)
%22 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.ArrayOf"(i64 8, ptr %21)
%23 = call %"github.com/goplus/llgo/runtime/abi.StructField" @"github.com/goplus/llgo/runtime/internal/runtime.StructField"(%"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @2, i64 4 }, ptr %22, i64 8, %"github.com/goplus/llgo/runtime/internal/runtime.String" zeroinitializer, i1 false)
%24 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 0)
%25 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" undef, ptr %24, 0
%26 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %25, i64 0, 1
%27 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %26, i64 0, 2
%28 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Interface"(%"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @0, i64 41 }, %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %27)
%29 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.ArrayOf"(i64 8, ptr %28)
%30 = call %"github.com/goplus/llgo/runtime/abi.StructField" @"github.com/goplus/llgo/runtime/internal/runtime.StructField"(%"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @3, i64 5 }, ptr %29, i64 136, %"github.com/goplus/llgo/runtime/internal/runtime.String" zeroinitializer, i1 false)
%31 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Basic"(i64 58)
%32 = call %"github.com/goplus/llgo/runtime/abi.StructField" @"github.com/goplus/llgo/runtime/internal/runtime.StructField"(%"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @4, i64 8 }, ptr %31, i64 264, %"github.com/goplus/llgo/runtime/internal/runtime.String" zeroinitializer, i1 false)
%33 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 224)
%34 = getelementptr %"github.com/goplus/llgo/runtime/abi.StructField", ptr %33, i64 0
store %"github.com/goplus/llgo/runtime/abi.StructField" %20, ptr %34, align 8
%35 = getelementptr %"github.com/goplus/llgo/runtime/abi.StructField", ptr %33, i64 1
store %"github.com/goplus/llgo/runtime/abi.StructField" %23, ptr %35, align 8
%36 = getelementptr %"github.com/goplus/llgo/runtime/abi.StructField", ptr %33, i64 2
store %"github.com/goplus/llgo/runtime/abi.StructField" %30, ptr %36, align 8
%37 = getelementptr %"github.com/goplus/llgo/runtime/abi.StructField", ptr %33, i64 3
store %"github.com/goplus/llgo/runtime/abi.StructField" %32, ptr %37, align 8
%38 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" undef, ptr %33, 0
%39 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %38, i64 4, 1
%40 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %39, i64 4, 2
%41 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Struct"(%"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @0, i64 41 }, i64 272, %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %40)
%42 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.MapOf"(ptr %12, ptr %17, ptr %41, i64 12)
call void @"github.com/goplus/llgo/runtime/internal/runtime.SetDirectIface"(ptr %42)
store ptr %42, ptr @"map[_llgo_string]_llgo_any", align 8
br label %_llgo_6
_llgo_6: ; preds = %_llgo_5, %_llgo_4
%43 = load ptr, ptr @_llgo_int, align 8
%44 = icmp eq ptr %43, null
br i1 %44, label %_llgo_7, label %_llgo_8
_llgo_7: ; preds = %_llgo_6
%45 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Basic"(i64 34)
store ptr %45, ptr @_llgo_int, align 8
br label %_llgo_8
_llgo_8: ; preds = %_llgo_7, %_llgo_6
ret void
}
declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.Basic"(i64)
declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.Interface"(%"github.com/goplus/llgo/runtime/internal/runtime.String", %"github.com/goplus/llgo/runtime/internal/runtime.Slice")
declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64)
declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.MapOf"(ptr, ptr, ptr, i64)
declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.Struct"(%"github.com/goplus/llgo/runtime/internal/runtime.String", i64, %"github.com/goplus/llgo/runtime/internal/runtime.Slice")
declare %"github.com/goplus/llgo/runtime/abi.StructField" @"github.com/goplus/llgo/runtime/internal/runtime.StructField"(%"github.com/goplus/llgo/runtime/internal/runtime.String", ptr, i64, %"github.com/goplus/llgo/runtime/internal/runtime.String", i1)
declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.ArrayOf"(i64, ptr)
declare void @"github.com/goplus/llgo/runtime/internal/runtime.SetDirectIface"(ptr)
declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.MakeMap"(ptr, i64)
declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.MapAssign"(ptr, ptr, ptr)
declare void @"github.com/goplus/llgo/runtime/internal/runtime.PrintString"(%"github.com/goplus/llgo/runtime/internal/runtime.String")
declare void @"github.com/goplus/llgo/runtime/internal/runtime.PrintByte"(i8)
declare void @"github.com/goplus/llgo/runtime/internal/runtime.PrintUint"(i64)

View File

@@ -270,6 +270,52 @@ func TestErrBuiltin(t *testing.T) {
test("atomicCmpXchg", func(ctx *context) { ctx.atomicCmpXchg(nil, nil) })
}
func TestErrAsm(t *testing.T) {
test := func(testName string, fn func(ctx *context)) {
defer func() {
if r := recover(); r == nil {
t.Fatal(testName, ": no error?")
}
}()
var ctx context
fn(&ctx)
}
test("asm(NoArgs)", func(ctx *context) { ctx.asm(nil, []ssa.Value{}) })
test("asm(Nonconst)", func(ctx *context) { ctx.asm(nil, []ssa.Value{&ssa.Parameter{}}) })
test("asmFull(Nonconst)", func(ctx *context) { ctx.asm(nil, []ssa.Value{&ssa.Parameter{}, &ssa.Parameter{}}) })
test("asmFull(NonConstKey)", func(ctx *context) {
makeMap := &ssa.MakeMap{}
nonConstKey := &ssa.Parameter{}
mapUpdate := &ssa.MapUpdate{Key: nonConstKey}
referrers := []ssa.Instruction{mapUpdate}
setRefs(unsafe.Pointer(makeMap), referrers...)
strConst := &ssa.Const{
Value: constant.MakeString("nop"),
}
ctx.asm(nil, []ssa.Value{strConst, makeMap})
})
test("asmFull(RegisterNotFound)", func(ctx *context) {
makeMap := &ssa.MakeMap{}
referrers := []ssa.Instruction{}
setRefs(unsafe.Pointer(makeMap), referrers...)
strConst := &ssa.Const{
Value: constant.MakeString("test {missing}"),
}
ctx.asm(nil, []ssa.Value{strConst, makeMap})
})
test("asmFull(UnknownReferrer)", func(ctx *context) {
makeMap := &ssa.MakeMap{}
unknownRef := &ssa.Return{}
referrers := []ssa.Instruction{unknownRef}
setRefs(unsafe.Pointer(makeMap), referrers...)
strConst := &ssa.Const{
Value: constant.MakeString("test"),
}
ctx.asm(nil, []ssa.Value{strConst, makeMap})
})
}
func TestPkgNoInit(t *testing.T) {
pkg := types.NewPackage("foo", "foo")
ctx := &context{

View File

@@ -17,15 +17,20 @@
package cl
import (
"fmt"
"go/constant"
"go/types"
"log"
"regexp"
"strings"
"golang.org/x/tools/go/ssa"
llssa "github.com/goplus/llgo/ssa"
)
var asmRegisterRegex = regexp.MustCompile(`\{[a-zA-Z]+\}`)
// -----------------------------------------------------------------------------
func constStr(v ssa.Value) (ret string, ok bool) {
@@ -67,14 +72,93 @@ func cstr(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) {
}
// func asm(string)
func asm(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) {
// func asm(string, map[string]any)
func (p *context) asm(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) {
if len(args) == 0 || len(args) > 2 {
panic("asm: invalid arguments - expected asm(<string-literal>) or asm(<string-literal>, <map-literal>)")
}
asmString, ok := constStr(args[0])
if !ok {
panic("asm: inline assembly requires a constant string")
}
if len(args) == 1 {
if sv, ok := constStr(args[0]); ok {
b.InlineAsm(sv)
b.InlineAsm(asmString)
return llssa.Expr{Type: b.Prog.Void()}
}
registers := make(map[string]llssa.Expr)
if registerMap, ok := args[1].(*ssa.MakeMap); ok {
referrers := registerMap.Referrers()
for _, r := range *referrers {
switch r := r.(type) {
case *ssa.DebugRef, *ssa.Call:
// ignore
case *ssa.MapUpdate:
if r.Block() != registerMap.Block() {
panic("asm: register value map must be created in the same basic block")
}
panic("asm(<string-literal>): invalid arguments")
key, ok := constStr(r.Key)
if !ok {
panic("asm: register key must be a string constant")
}
llvmValue := p.compileValue(b, r.Value.(*ssa.MakeInterface).X)
registers[key] = llvmValue
default:
panic(fmt.Sprintf("asm: don't know how to handle argument to inline assembly: %s", r.String()))
}
}
}
finalAsm := asmString
var hasOutput bool
var inputValues []llssa.Expr
var constraints []string
registerNumbers := map[string]int{}
if strings.Contains(finalAsm, "{}") {
finalAsm = strings.ReplaceAll(finalAsm, "{}", "$0")
constraints = append(constraints, "=&r")
registerNumbers[""] = 0
hasOutput = true
}
finalAsm = asmRegisterRegex.ReplaceAllStringFunc(finalAsm, func(s string) string {
// TODO: skip strings like {r4} etc. that look like ARM push/pop
// instructions.
name := s[1 : len(s)-1]
value, ok := registers[name]
if !ok {
panic(fmt.Sprintf("asm: register not found: %s", name))
}
if _, ok := registerNumbers[name]; !ok {
// Type checking - only allow integer basic types
rawType := value.Type.RawType()
if basic, ok := rawType.Underlying().(*types.Basic); ok && basic.Info()&types.IsInteger != 0 {
registerNumbers[name] = len(registerNumbers)
inputValues = append(inputValues, value)
constraints = append(constraints, "r")
} else {
// Pointer operands support was dropped, following TinyGo
// NOTE(tinygo): Memory references require a type starting with LLVM 14, probably as a preparation for opaque pointers.
panic(fmt.Sprintf("asm: unsupported type in inline assembly for operand: %s, only integer types are supported", name))
}
}
return fmt.Sprintf("${%v}", registerNumbers[name])
})
constraintStr := strings.Join(constraints, ",")
if debugInstr {
log.Printf("asm: %q -> %q, constraints: %q", asmString, finalAsm, constraintStr)
}
if !hasOutput {
// Make sure we return something valid
b.InlineAsmFull(finalAsm, constraintStr, b.Prog.Void(), inputValues)
return b.Prog.Val((uintptr(0)))
}
return b.InlineAsmFull(finalAsm, constraintStr, b.Prog.Uintptr(), inputValues)
}
// -----------------------------------------------------------------------------
@@ -470,7 +554,7 @@ func (p *context) call(b llssa.Builder, act llssa.DoAction, call *ssa.CallCommon
case llgoCstr:
ret = cstr(b, args)
case llgoAsm:
ret = asm(b, args)
ret = p.asm(b, args)
case llgoCgoCString:
ret = p.cgoCString(b, args)
case llgoCgoCBytes:

2
go.mod
View File

@@ -7,7 +7,7 @@ toolchain go1.24.1
require (
github.com/goplus/cobra v1.9.12 //gop:class
github.com/goplus/gogen v1.19.1
github.com/goplus/lib v0.2.0
github.com/goplus/lib v0.3.0
github.com/goplus/llgo/runtime v0.0.0-00010101000000-000000000000
github.com/goplus/llvm v0.8.5
github.com/goplus/mod v0.17.1

4
go.sum
View File

@@ -4,8 +4,8 @@ github.com/goplus/cobra v1.9.12 h1:0F9EdEbeGyITGz+mqoHoJ5KpUw97p1CkxV74IexHw5s=
github.com/goplus/cobra v1.9.12/go.mod h1:p4LhfNJDKEpiGjGiNn0crUXL5dUPA5DX2ztYpEJR34E=
github.com/goplus/gogen v1.19.1 h1:L7jz60azeowj8zUq48tozETriTPBLqHb0nDj6PheANc=
github.com/goplus/gogen v1.19.1/go.mod h1:owX2e1EyU5WD+Nm6oH2m/GXjLdlBYcwkLO4wN8HHXZI=
github.com/goplus/lib v0.2.0 h1:AjqkN1XK5H23wZMMlpaUYAMCDAdSBQ2NMFrLtSh7W4g=
github.com/goplus/lib v0.2.0/go.mod h1:SgJv3oPqLLHCu0gcL46ejOP3x7/2ry2Jtxu7ta32kp0=
github.com/goplus/lib v0.3.0 h1:y0ZGb5Q/RikW1oMMB4Di7XIZIpuzh/7mlrR8HNbxXCA=
github.com/goplus/lib v0.3.0/go.mod h1:SgJv3oPqLLHCu0gcL46ejOP3x7/2ry2Jtxu7ta32kp0=
github.com/goplus/llvm v0.8.5 h1:DUnFeYC3Rco622tBEKGg8xkigRAV2fh5ZIfBCt7gOSs=
github.com/goplus/llvm v0.8.5/go.mod h1:PeVK8GgzxwAYCiMiUAJb5wJR6xbhj989tu9oulKLLT4=
github.com/goplus/mod v0.17.1 h1:ITovxDcc5zbURV/Wrp3/SBsYLgC1KrxY6pq1zMM2V94=

View File

@@ -742,16 +742,20 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l
}
if IsFullRpathEnabled() {
exargs := make([]string, 0, ctx.nLibdir<<1)
// Treat every link-time library search path, specified by the -L parameter, as a runtime search path as well.
// This is to ensure the final executable can locate libraries with a relocatable install_name
// (e.g., "@rpath/libfoo.dylib") at runtime.
rpaths := make(map[string]none)
for _, arg := range linkArgs {
if strings.HasPrefix(arg, "-L") {
exargs = append(exargs, "-rpath", arg[2:])
path := arg[2:]
if _, ok := rpaths[path]; ok {
continue
}
rpaths[path] = none{}
linkArgs = append(linkArgs, "-rpath", path)
}
}
linkArgs = append(linkArgs, exargs...)
}
err = linkObjFiles(ctx, orgApp, objFiles, linkArgs, verbose)
@@ -836,15 +840,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"
}
}
@@ -901,10 +906,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

@@ -446,6 +446,12 @@ func TestFlagMergingScenarios(t *testing.T) {
expectComp: []string{"-O3", "-fPIC", "-Wall", "-Wextra", "-std=c11"},
expectLink: []string{"-O3", "-lm", "-lpthread", "-static"},
},
{
// case from https://github.com/goplus/llgo/issues/1244
name: "issue 1244",
envCFlags: "-w -pipe -mmacosx-version-min=15 -isysroot/Library/Developer/CommandLineTools/SDKs/MacOSX15.sdk",
expectComp: []string{"-w", "-pipe", "-mmacosx-version-min=15", "-isysroot/Library/Developer/CommandLineTools/SDKs/MacOSX15.sdk"},
},
}
// Save original environment

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
@@ -219,6 +221,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, forceEspClang bool) (export Export, err error) {
targetTriple := llvm.GetTargetTriple(goos, goarch)
llgoRoot := env.LLGoROOT()
@@ -583,6 +614,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
@@ -595,7 +677,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, targetName string, wasiThreads, forceEspClang bool) (export Export, err error) {
if targetName != "" {
if targetName != "" && !strings.HasPrefix(targetName, "wasm") && !strings.HasPrefix(targetName, "wasi") {
return useTarget(targetName)
}
return use(goos, goarch, wasiThreads, forceEspClang)

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", "wasi", false, true)
export, err := Use("linux", "amd64", "esp32", false, true)
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)
}
})
}

4
internal/env/env.go vendored
View File

@@ -97,12 +97,12 @@ func isLLGoRoot(root string) (string, bool) {
return "", false
}
// Check for go.mod
data, err := os.ReadFile(filepath.Join(root, "go.mod"))
data, err := os.ReadFile(filepath.Join(root, LLGoRuntimePkgName, "go.mod"))
if err != nil {
return "", false
}
// Check module name
if !strings.Contains(string(data), "module "+LLGoCompilerPkg+"\n") {
if !strings.Contains(string(data), "module "+LLGoRuntimePkg+"\n") {
return "", false
}
return root, true

View File

@@ -41,8 +41,10 @@ func TestLLGoRuntimeDir(t *testing.T) {
defer os.Setenv("LLGO_ROOT", origLLGoRoot)
tmpDir := t.TempDir()
goModContent := []byte("module github.com/goplus/llgo\n")
if err := os.WriteFile(filepath.Join(tmpDir, "go.mod"), goModContent, 0644); err != nil {
runtimeDir := filepath.Join(tmpDir, "runtime")
os.MkdirAll(runtimeDir, 0755)
goModContent := []byte("module github.com/goplus/llgo/runtime\n")
if err := os.WriteFile(filepath.Join(runtimeDir, "go.mod"), goModContent, 0644); err != nil {
t.Fatal(err)
}
@@ -92,8 +94,10 @@ func TestLLGoROOT(t *testing.T) {
defer os.Setenv("LLGO_ROOT", origLLGoRoot)
tmpDir := t.TempDir()
goModContent := []byte("module github.com/goplus/llgo\n")
if err := os.WriteFile(filepath.Join(tmpDir, "go.mod"), goModContent, 0644); err != nil {
runtimeDir := filepath.Join(tmpDir, "runtime")
os.MkdirAll(runtimeDir, 0755)
goModContent := []byte("module github.com/goplus/llgo/runtime\n")
if err := os.WriteFile(filepath.Join(runtimeDir, "go.mod"), goModContent, 0644); err != nil {
t.Fatal(err)
}
@@ -170,8 +174,10 @@ func TestIsLLGoRoot(t *testing.T) {
// Test with valid path and valid go.mod
t.Run("valid path and go.mod", func(t *testing.T) {
tmpDir := t.TempDir()
goModContent := []byte("module github.com/goplus/llgo\n")
if err := os.WriteFile(filepath.Join(tmpDir, "go.mod"), goModContent, 0644); err != nil {
runtimeDir := filepath.Join(tmpDir, "runtime")
os.MkdirAll(runtimeDir, 0755)
goModContent := []byte("module github.com/goplus/llgo/runtime\n")
if err := os.WriteFile(filepath.Join(runtimeDir, "go.mod"), goModContent, 0644); err != nil {
t.Fatal(err)
}

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

@@ -3,7 +3,8 @@ package targets
import (
"fmt"
"path/filepath"
"runtime"
"github.com/goplus/llgo/internal/env"
)
// Resolver provides high-level interface for target configuration resolution
@@ -20,10 +21,8 @@ func NewResolver(targetsDir string) *Resolver {
// NewDefaultResolver creates a resolver with default targets directory
func NewDefaultResolver() *Resolver {
// Assume targets directory is relative to this package
_, filename, _, _ := runtime.Caller(0)
projectRoot := filepath.Dir(filepath.Dir(filepath.Dir(filename)))
targetsDir := filepath.Join(projectRoot, "targets")
llgoRoot := env.LLGoROOT()
targetsDir := filepath.Join(llgoRoot, "targets")
return NewResolver(targetsDir)
}

View File

@@ -0,0 +1,17 @@
package os
import (
_ "unsafe"
c "github.com/goplus/llgo/runtime/internal/clite"
)
type DIR struct {
Unused [0]byte
}
//go:linkname Opendir C.opendir
func Opendir(name *c.Char) *DIR
//go:linkname Closedir C.closedir
func Closedir(dir *DIR) c.Int

View File

@@ -0,0 +1,17 @@
package os
import (
_ "unsafe"
c "github.com/goplus/llgo/runtime/internal/clite"
"github.com/goplus/llgo/runtime/internal/clite/syscall"
)
//go:linkname Fdopendir C.fdopendir$INODE64
func Fdopendir(fd c.Int) *DIR
//go:linkname Readdir C.readdir$INODE64
func Readdir(dir *DIR) *syscall.Dirent
//go:linkname Fstatat C.fstatat$INODE64
func Fstatat(dirfd c.Int, path *c.Char, buf *StatT, flags c.Int) c.Int

View File

@@ -0,0 +1,20 @@
//go:build !(darwin && amd64)
// +build !darwin !amd64
package os
import (
_ "unsafe"
c "github.com/goplus/llgo/runtime/internal/clite"
"github.com/goplus/llgo/runtime/internal/clite/syscall"
)
//go:linkname Fdopendir C.fdopendir
func Fdopendir(fd c.Int) *DIR
//go:linkname Readdir C.readdir
func Readdir(dir *DIR) *syscall.Dirent
//go:linkname Fstatat C.fstatat
func Fstatat(dirfd c.Int, path *c.Char, buf *StatT, flags c.Int) c.Int

View File

@@ -135,9 +135,6 @@ func Fchmodat(dirfd c.Int, path *c.Char, mode ModeT, flags c.Int) c.Int
//go:linkname Fchownat C.fchownat
func Fchownat(dirfd c.Int, path *c.Char, owner UidT, group GidT, flags c.Int) c.Int
//go:linkname Fstatat C.fstatat
func Fstatat(dirfd c.Int, path *c.Char, buf *StatT, flags c.Int) c.Int
// -----------------------------------------------------------------------------
//go:linkname Open C.open
@@ -191,9 +188,6 @@ func Fchmod(fd c.Int, mode ModeT) c.Int
//go:linkname Fchown C.fchown
func Fchown(fd c.Int, owner UidT, group GidT) c.Int
//go:linkname Fstat C.fstat
func Fstat(fd c.Int, buf *StatT) c.Int
//go:linkname Isatty C.isatty
func Isatty(fd c.Int) c.Int

View File

@@ -14,3 +14,6 @@ func Stat(path *c.Char, buf *StatT) c.Int
//go:linkname Lstat C.lstat
func Lstat(path *c.Char, buf *StatT) c.Int
//go:linkname Fstat C.fstat
func Fstat(fd c.Int, buf *StatT) c.Int

View File

@@ -11,3 +11,6 @@ func Stat(path *c.Char, buf *StatT) c.Int
//go:linkname Lstat C.lstat64
func Lstat(path *c.Char, buf *StatT) c.Int
//go:linkname Fstat C.fstat64
func Fstat(fd c.Int, buf *StatT) c.Int

View File

@@ -82,30 +82,10 @@ func ReadDir(name string) ([]DirEntry, error) {
return dirs, err
}
//go:linkname c_fdopendir C.fdopendir
func c_fdopendir(fd c.Int) uintptr
func fdopendir(fd int) (dir uintptr, err error) {
return c_fdopendir(c.Int(fd)), nil
}
//go:linkname c_closedir C.closedir
func c_closedir(dir uintptr) c.Int
func closedir(dir uintptr) error {
if c_closedir(dir) != 0 {
return syscall.Errno(os.Errno())
}
return nil
}
//go:linkname c_readdir C.readdir
func c_readdir(dir uintptr) *syscall.Dirent
func readdir(dir uintptr) ([]syscall.Dirent, error) {
func readdir(dir *os.DIR) ([]syscall.Dirent, error) {
var entries []syscall.Dirent
for {
dirent := c_readdir(dir)
dirent := os.Readdir(dir)
if dirent == nil {
break
}
@@ -139,11 +119,11 @@ func (f *File) ReadDir(n int) (dirents []DirEntry, err error) {
}
// Open directory using file descriptor
dir, err := fdopendir(int(f.fd))
if err != nil {
return nil, err
dir := os.Fdopendir(c.Int(f.fd))
if dir == nil {
return nil, syscall.Errno(os.Errno())
}
defer closedir(dir)
defer os.Closedir(dir)
// Match Readdir and Readdirnames: don't return nil slices.
dirents = []DirEntry{}

View File

@@ -286,6 +286,18 @@ func (b Builder) InlineAsm(instruction string) {
b.impl.CreateCall(typ, asm, nil, "")
}
func (b Builder) InlineAsmFull(instruction, constraints string, retType Type, exprs []Expr) Expr {
typs := make([]llvm.Type, len(exprs))
vals := make([]llvm.Value, len(exprs))
for i, expr := range exprs {
typs[i], vals[i] = expr.Type.ll, expr.impl
}
ftype := llvm.FunctionType(retType.ll, typs, false)
asm := llvm.InlineAsm(ftype, instruction, constraints, true, false, llvm.InlineAsmDialectATT, false)
return Expr{b.impl.CreateCall(ftype, asm, vals, ""), retType}
}
// GoString returns a Go string
func (b Builder) GoString(v Expr) Expr {
fn := b.Pkg.rtFunc("GoString")

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"

View File

@@ -49,6 +49,13 @@ func SplitPkgConfigFlags(s string) []string {
for i < len(s) && (s[i] == ' ' || s[i] == '\t') {
i++
}
// Check if next character is another flag (short flag with no argument)
if i < len(s) && s[i] == '-' {
// This is a short flag with no argument, finish current flag
continue
}
// Read content until next space
for i < len(s) {
if s[i] == '\\' && i+1 < len(s) && (s[i+1] == ' ' || s[i+1] == '\t') {

View File

@@ -50,8 +50,8 @@ func TestSplitPkgConfigFlags(t *testing.T) {
})
t.Run("consecutive_flags", func(t *testing.T) {
ftest("-I -L", `["-I-L"]`)
ftest("-I -L /usr/lib", `["-I-L /usr/lib"]`)
ftest("-I -L", `["-I" "-L"]`)
ftest("-I -L /usr/lib", `["-I" "-L/usr/lib"]`)
})
t.Run("edge_cases", func(t *testing.T) {
@@ -59,7 +59,7 @@ func TestSplitPkgConfigFlags(t *testing.T) {
ftest(" ", "[]")
ftest("-", `["-"]`)
ftest("-I", `["-I"]`)
ftest("-I -", `["-I-"]`)
ftest("-I -", `["-I" "-"]`)
})
t.Run("escaped_spaces", func(t *testing.T) {
@@ -77,6 +77,12 @@ func TestSplitPkgConfigFlags(t *testing.T) {
ftest("-DVERSION=2.1 -DDEBUG=1", `["-DVERSION=2.1" "-DDEBUG=1"]`)
ftest("-D VERSION=2.1 -D DEBUG=1", `["-DVERSION=2.1" "-DDEBUG=1"]`)
})
// case for https://github.com/goplus/llgo/issues/1244
t.Run("w_pipe", func(t *testing.T) {
ftest("-w -pipe", `["-w" "-pipe"]`)
ftest("-Os -w -pipe", `["-Os" "-w" "-pipe"]`)
})
}
func toString(ss []string) string {