diff --git a/_demo/chandemo/chan.go b/_cmptest/_chandemo/chan.go similarity index 100% rename from _demo/chandemo/chan.go rename to _cmptest/_chandemo/chan.go diff --git a/_demo/_timeout/timer.go b/_cmptest/_timeout/timer.go similarity index 100% rename from _demo/_timeout/timer.go rename to _cmptest/_timeout/timer.go diff --git a/_demo/cexec/exec.go b/_demo/cexec/exec.go index f89ebba0..203c9764 100644 --- a/_demo/cexec/exec.go +++ b/_demo/cexec/exec.go @@ -1,20 +1,11 @@ package main import ( - "runtime" - "unsafe" - "github.com/goplus/llgo/c" "github.com/goplus/llgo/c/os" ) func main() { ls := c.Str("ls") - args := []*c.Char{ls, c.Str("-l"), nil} - if runtime.GOOS == "windows" { - ls = c.Str("dir") - args = []*c.Char{ls, nil} - } - - os.Execvp(ls, unsafe.SliceData(args)) + os.Execlp(ls, ls, c.Str("-l"), nil) } diff --git a/_demo/sysexec/exec.go b/_demo/sysexec/exec.go index b4aafa20..93d9e98b 100644 --- a/_demo/sysexec/exec.go +++ b/_demo/sysexec/exec.go @@ -9,10 +9,10 @@ import ( func main() { ls := "ls" - args := []string{"ls", "-l"} + args := []string{ls, "-l"} if runtime.GOOS == "windows" { ls = "dir" - args = nil + args = []string{ls} } lspath, _ := exec.LookPath(ls) if lspath != "" { diff --git a/c/c.go b/c/c.go index 37bc6dbf..9a20d8b1 100644 --- a/c/c.go +++ b/c/c.go @@ -68,6 +68,9 @@ func Alloca(size uintptr) Pointer //go:linkname AllocaCStr llgo.allocaCStr func AllocaCStr(s string) *Char +//go:linkname AllocaCStrs llgo.allocaCStrs +func AllocaCStrs(strs []string, endWithNil bool) **Char + // TODO(xsw): // llgo:link AllocaNew llgo.allocaNew func AllocaNew[T any]() *T { return nil } diff --git a/c/syscall/unix/unix.go b/c/syscall/unix/unix.go new file mode 100644 index 00000000..ab5144f6 --- /dev/null +++ b/c/syscall/unix/unix.go @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package unix + +const ( + LLGoPackage = "decl" +) diff --git a/cl/_testlibc/allocacstrs/in.go b/cl/_testlibc/allocacstrs/in.go new file mode 100644 index 00000000..6df19ea5 --- /dev/null +++ b/cl/_testlibc/allocacstrs/in.go @@ -0,0 +1,16 @@ +package main + +import "github.com/goplus/llgo/c" + +func main() { + cstrs := c.AllocaCStrs([]string{"a", "b", "c"}, true) + n := 0 + for { + cstr := *c.Advance(cstrs, n) + if cstr == nil { + break + } + c.Printf(c.Str("%s\n"), cstr) + n++ + } +} diff --git a/cl/_testlibc/allocacstrs/out.ll b/cl/_testlibc/allocacstrs/out.ll new file mode 100644 index 00000000..d6453828 --- /dev/null +++ b/cl/_testlibc/allocacstrs/out.ll @@ -0,0 +1,124 @@ +; ModuleID = 'main' +source_filename = "main" + +%"github.com/goplus/llgo/internal/runtime.String" = type { ptr, i64 } +%"github.com/goplus/llgo/internal/runtime.Slice" = type { ptr, i64, i64 } + +@"main.init$guard" = global i1 false, align 1 +@__llgo_argc = global i32 0, align 4 +@__llgo_argv = global ptr null, align 8 +@0 = private unnamed_addr constant [1 x i8] c"a", align 1 +@1 = private unnamed_addr constant [1 x i8] c"b", align 1 +@2 = private unnamed_addr constant [1 x i8] c"c", align 1 +@3 = private unnamed_addr constant [4 x i8] c"%s\0A\00", align 1 + +define void @main.init() { +_llgo_0: + %0 = load i1, ptr @"main.init$guard", align 1 + br i1 %0, label %_llgo_2, label %_llgo_1 + +_llgo_1: ; preds = %_llgo_0 + store i1 true, ptr @"main.init$guard", align 1 + br label %_llgo_2 + +_llgo_2: ; preds = %_llgo_1, %_llgo_0 + ret void +} + +define i32 @main(i32 %0, ptr %1) { +_llgo_0: + store i32 %0, ptr @__llgo_argc, align 4 + store ptr %1, ptr @__llgo_argv, align 8 + call void @"github.com/goplus/llgo/internal/runtime.init"() + call void @main.init() + %2 = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 48) + %3 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %2, i64 0 + %4 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 + %5 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %4, i32 0, i32 0 + store ptr @0, ptr %5, align 8 + %6 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %4, i32 0, i32 1 + store i64 1, ptr %6, align 4 + %7 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %4, align 8 + store %"github.com/goplus/llgo/internal/runtime.String" %7, ptr %3, align 8 + %8 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %2, i64 1 + %9 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 + %10 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %9, i32 0, i32 0 + store ptr @1, ptr %10, align 8 + %11 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %9, i32 0, i32 1 + store i64 1, ptr %11, align 4 + %12 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %9, align 8 + store %"github.com/goplus/llgo/internal/runtime.String" %12, ptr %8, align 8 + %13 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %2, i64 2 + %14 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 + %15 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %14, i32 0, i32 0 + store ptr @2, ptr %15, align 8 + %16 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %14, i32 0, i32 1 + store i64 1, ptr %16, align 4 + %17 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %14, align 8 + store %"github.com/goplus/llgo/internal/runtime.String" %17, ptr %13, align 8 + %18 = alloca %"github.com/goplus/llgo/internal/runtime.Slice", align 8 + %19 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Slice", ptr %18, i32 0, i32 0 + store ptr %2, ptr %19, align 8 + %20 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Slice", ptr %18, i32 0, i32 1 + store i64 3, ptr %20, align 4 + %21 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Slice", ptr %18, i32 0, i32 2 + store i64 3, ptr %21, align 4 + %22 = load %"github.com/goplus/llgo/internal/runtime.Slice", ptr %18, align 8 + %23 = extractvalue %"github.com/goplus/llgo/internal/runtime.Slice" %22, 1 + %24 = add i64 %23, 1 + %25 = alloca ptr, i64 %24, align 8 + br label %_llgo_4 + +_llgo_1: ; preds = %_llgo_3, %_llgo_6 + %26 = phi i64 [ 0, %_llgo_6 ], [ %31, %_llgo_3 ] + %27 = getelementptr ptr, ptr %25, i64 %26 + %28 = load ptr, ptr %27, align 8 + %29 = icmp eq ptr %28, null + br i1 %29, label %_llgo_2, label %_llgo_3 + +_llgo_2: ; preds = %_llgo_1 + ret i32 0 + +_llgo_3: ; preds = %_llgo_1 + %30 = call i32 (ptr, ...) @printf(ptr @3, ptr %28) + %31 = add i64 %26, 1 + br label %_llgo_1 + +_llgo_4: ; preds = %_llgo_5, %_llgo_0 + %32 = phi i64 [ 0, %_llgo_0 ], [ %46, %_llgo_5 ] + %33 = icmp slt i64 %32, %23 + br i1 %33, label %_llgo_5, label %_llgo_6 + +_llgo_5: ; preds = %_llgo_4 + %34 = extractvalue %"github.com/goplus/llgo/internal/runtime.Slice" %22, 0 + %35 = extractvalue %"github.com/goplus/llgo/internal/runtime.Slice" %22, 1 + %36 = icmp slt i64 %32, 0 + %37 = icmp sge i64 %32, %35 + %38 = or i1 %37, %36 + call void @"github.com/goplus/llgo/internal/runtime.AssertIndexRange"(i1 %38) + %39 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %34, i64 %32 + %40 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %39, align 8 + %41 = getelementptr ptr, ptr %25, i64 %32 + %42 = extractvalue %"github.com/goplus/llgo/internal/runtime.String" %40, 1 + %43 = add i64 %42, 1 + %44 = alloca i8, i64 %43, align 1 + %45 = call ptr @"github.com/goplus/llgo/internal/runtime.CStrCopy"(ptr %44, %"github.com/goplus/llgo/internal/runtime.String" %40) + store ptr %45, ptr %41, align 8 + %46 = add i64 %32, 1 + br label %_llgo_4 + +_llgo_6: ; preds = %_llgo_4 + %47 = getelementptr ptr, ptr %25, i64 %23 + store ptr null, ptr %47, align 8 + br label %_llgo_1 +} + +declare void @"github.com/goplus/llgo/internal/runtime.init"() + +declare ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64) + +declare void @"github.com/goplus/llgo/internal/runtime.AssertIndexRange"(i1) + +declare ptr @"github.com/goplus/llgo/internal/runtime.CStrCopy"(ptr, %"github.com/goplus/llgo/internal/runtime.String") + +declare i32 @printf(ptr, ...) diff --git a/cl/builtin_test.go b/cl/builtin_test.go index 5c479af5..e2b0fce2 100644 --- a/cl/builtin_test.go +++ b/cl/builtin_test.go @@ -27,6 +27,12 @@ import ( "golang.org/x/tools/go/ssa" ) +func TestConstBool(t *testing.T) { + if v, ok := constBool(nil); v || ok { + t.Fatal("constBool?") + } +} + func TestToBackground(t *testing.T) { if v := toBackground(""); v != llssa.InGo { t.Fatal("toBackground:", v) @@ -143,6 +149,8 @@ func TestErrBuiltin(t *testing.T) { test("advance", func(ctx *context) { ctx.advance(nil, nil) }) test("alloca", func(ctx *context) { ctx.alloca(nil, nil) }) test("allocaCStr", func(ctx *context) { ctx.allocaCStr(nil, nil) }) + test("allocaCStrs", func(ctx *context) { ctx.allocaCStrs(nil, nil) }) + test("allocaCStrs(Nonconst)", func(ctx *context) { ctx.allocaCStrs(nil, []ssa.Value{nil, &ssa.Parameter{}}) }) test("string", func(ctx *context) { ctx.string(nil, nil) }) test("stringData", func(ctx *context) { ctx.stringData(nil, nil) }) test("funcAddr", func(ctx *context) { ctx.funcAddr(nil, nil) }) diff --git a/cl/import.go b/cl/import.go index dcd61499..f779de89 100644 --- a/cl/import.go +++ b/cl/import.go @@ -381,17 +381,19 @@ const ( llgoCstr = llgoInstrBase + 1 llgoAlloca = llgoInstrBase + 2 llgoAllocaCStr = llgoInstrBase + 3 - llgoAdvance = llgoInstrBase + 4 - llgoIndex = llgoInstrBase + 5 - llgoDeferData = llgoInstrBase + 6 + llgoAllocaCStrs = llgoInstrBase + 4 + llgoAdvance = llgoInstrBase + 5 + llgoIndex = llgoInstrBase + 6 llgoStringData = llgoInstrBase + 7 llgoString = llgoInstrBase + 8 - llgoFuncAddr = llgoInstrBase + 9 + llgoDeferData = llgoInstrBase + 9 llgoSigjmpbuf = llgoInstrBase + 0xa llgoSigsetjmp = llgoInstrBase + 0xb llgoSiglongjmp = llgoInstrBase + 0xc + llgoFuncAddr = llgoInstrBase + 0xd + llgoPyList = llgoInstrBase + 0x10 llgoPyStr = llgoInstrBase + 0x11 diff --git a/cl/instr.go b/cl/instr.go index 79890d7a..83837ad0 100644 --- a/cl/instr.go +++ b/cl/instr.go @@ -28,14 +28,29 @@ import ( // ----------------------------------------------------------------------------- +func constStr(v ssa.Value) (ret string, ok bool) { + if c, ok := v.(*ssa.Const); ok { + if v := c.Value; v.Kind() == constant.String { + return constant.StringVal(v), true + } + } + return +} + +func constBool(v ssa.Value) (ret bool, ok bool) { + if c, ok := v.(*ssa.Const); ok { + if v := c.Value; v.Kind() == constant.Bool { + return constant.BoolVal(v), true + } + } + return +} + // func pystr(string) *py.Object func pystr(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { if len(args) == 1 { - if c, ok := args[0].(*ssa.Const); ok { - if v := c.Value; v.Kind() == constant.String { - sv := constant.StringVal(v) - return b.PyStr(sv) - } + if sv, ok := constStr(args[0]); ok { + return b.PyStr(sv) } } panic("pystr(): invalid arguments") @@ -44,11 +59,8 @@ func pystr(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { // func cstr(string) *int8 func cstr(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { if len(args) == 1 { - if c, ok := args[0].(*ssa.Const); ok { - if v := c.Value; v.Kind() == constant.String { - sv := constant.StringVal(v) - return b.CStr(sv) - } + if sv, ok := constStr(args[0]); ok { + return b.CStr(sv) } } panic("cstr(): invalid arguments") @@ -87,6 +99,19 @@ func (p *context) allocaCStr(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) panic("allocaCStr(s string): invalid arguments") } +// func allocaCStrs(strs []string, endWithNil bool) **int8 +func (p *context) allocaCStrs(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { + if len(args) == 2 { + endWithNil, ok := constBool(args[1]) + if !ok { + panic("allocaCStrs(strs, endWithNil): endWithNil should be constant bool") + } + strs := p.compileValue(b, args[0]) + return b.AllocaCStrs(strs, endWithNil) + } + panic("allocaCStrs(strs []string, endWithNil bool): invalid arguments") +} + // func string(cstr *int8, n ...int) *int8 func (p *context) string(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { if len(args) == 2 { @@ -185,6 +210,7 @@ var llgoInstrs = map[string]int{ "index": llgoIndex, "alloca": llgoAlloca, "allocaCStr": llgoAllocaCStr, + "allocaCStrs": llgoAllocaCStrs, "string": llgoString, "stringData": llgoStringData, "funcAddr": llgoFuncAddr, @@ -340,6 +366,8 @@ func (p *context) call(b llssa.Builder, act llssa.DoAction, call *ssa.CallCommon ret = p.alloca(b, args) case llgoAllocaCStr: ret = p.allocaCStr(b, args) + case llgoAllocaCStrs: + ret = p.allocaCStrs(b, args) case llgoString: ret = p.string(b, args) case llgoStringData: diff --git a/internal/lib/syscall/exec_unix.go b/internal/lib/syscall/exec_unix.go index 728103f2..7b4d3d90 100644 --- a/internal/lib/syscall/exec_unix.go +++ b/internal/lib/syscall/exec_unix.go @@ -10,6 +10,10 @@ package syscall import ( "sync" + "syscall" + + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/c/os" ) // ForkLock is used to synchronize creation of new file descriptors @@ -102,8 +106,10 @@ type ProcAttr struct { Sys *SysProcAttr } +/* TODO(xsw): var zeroProcAttr ProcAttr var zeroSysProcAttr SysProcAttr +*/ func forkExec(argv0 string, argv []string, attr *ProcAttr) (pid int, err error) { /* TODO(xsw): @@ -237,47 +243,9 @@ var execveOpenBSD func(path *byte, argv **byte, envp **byte) error // Exec invokes the execve(2) system call. func Exec(argv0 string, argv []string, envv []string) (err error) { - /* TODO(xsw): - argv0p, err := BytePtrFromString(argv0) - if err != nil { - return err + ret := os.Execve(c.AllocaCStr(argv0), c.AllocaCStrs(argv, true), c.AllocaCStrs(envv, true)) + if ret == 0 { + return nil } - argvp, err := SlicePtrFromStrings(argv) - if err != nil { - return err - } - envvp, err := SlicePtrFromStrings(envv) - if err != nil { - return err - } - runtime_BeforeExec() - - rlim, rlimOK := origRlimitNofile.Load().(Rlimit) - if rlimOK && rlim.Cur != 0 { - Setrlimit(RLIMIT_NOFILE, &rlim) - } - - var err1 error - if runtime.GOOS == "solaris" || runtime.GOOS == "illumos" || runtime.GOOS == "aix" { - // RawSyscall should never be used on Solaris, illumos, or AIX. - err1 = execveLibc( - uintptr(unsafe.Pointer(argv0p)), - uintptr(unsafe.Pointer(&argvp[0])), - uintptr(unsafe.Pointer(&envvp[0]))) - } else if runtime.GOOS == "darwin" || runtime.GOOS == "ios" { - // Similarly on Darwin. - err1 = execveDarwin(argv0p, &argvp[0], &envvp[0]) - } else if runtime.GOOS == "openbsd" && (runtime.GOARCH == "386" || runtime.GOARCH == "amd64" || runtime.GOARCH == "arm" || runtime.GOARCH == "arm64") { - // Similarly on OpenBSD. - err1 = execveOpenBSD(argv0p, &argvp[0], &envvp[0]) - } else { - _, _, err1 = RawSyscall(SYS_EXECVE, - uintptr(unsafe.Pointer(argv0p)), - uintptr(unsafe.Pointer(&argvp[0])), - uintptr(unsafe.Pointer(&envvp[0]))) - } - runtime_AfterExec() - return err1 - */ - panic("todo: syscall.Exec") + return syscall.Errno(ret) } diff --git a/ssa/memory.go b/ssa/memory.go index b021ca00..d14fd71b 100644 --- a/ssa/memory.go +++ b/ssa/memory.go @@ -144,6 +144,15 @@ func (b Builder) Alloca(n Expr) (ret Expr) { return } +/* TODO(xsw): +// AllocaU allocates uninitialized space for n*sizeof(elem) bytes. +func (b Builder) AllocaU(elem Type, n ...int64) (ret Expr) { + prog := b.Prog + size := SizeOf(prog, elem, n...) + return Expr{b.Alloca(size).impl, prog.Pointer(elem)} +} +*/ + // AllocaCStr allocates space for copy it from a Go string. func (b Builder) AllocaCStr(gostr Expr) (ret Expr) { if debugInstr { @@ -155,6 +164,30 @@ func (b Builder) AllocaCStr(gostr Expr) (ret Expr) { return b.InlineCall(b.Pkg.rtFunc("CStrCopy"), cstr, gostr) } +// func allocaCStrs(strs []string, endWithNil bool) **int8 +func (b Builder) AllocaCStrs(strs Expr, endWithNil bool) (cstrs Expr) { + if debugInstr { + log.Printf("AllocaCStrs %v, %v\n", strs.impl, endWithNil) + } + prog := b.Prog + n := b.SliceLen(strs) + n1 := n + if endWithNil { + n1 = b.BinOp(token.ADD, n, prog.Val(1)) + } + tcstr := prog.CStr() + cstrs = b.ArrayAlloca(tcstr, n1) + b.Times(n, func(i Expr) { + pstr := b.IndexAddr(strs, i) + s := b.Load(pstr) + b.Store(b.Advance(cstrs, i), b.AllocaCStr(s)) + }) + if endWithNil { + b.Store(b.Advance(cstrs, n), prog.Nil(tcstr)) + } + return +} + // ----------------------------------------------------------------------------- func (p Program) tyMalloc() *types.Signature { @@ -189,18 +222,17 @@ func (b Builder) free(ptr Expr) Expr { // ----------------------------------------------------------------------------- -/* // ArrayAlloca reserves space for an array of n elements of type telem. func (b Builder) ArrayAlloca(telem Type, n Expr) (ret Expr) { if debugInstr { - log.Printf("ArrayAlloca %v, %v\n", telem.t, n.impl) + log.Printf("ArrayAlloca %v, %v\n", telem.raw.Type, n.impl) } ret.impl = llvm.CreateArrayAlloca(b.impl, telem.ll, n.impl) ret.Type = b.Prog.Pointer(telem) return } -*/ +/* TODO(xsw): // ArrayAlloc allocates zero initialized space for an array of n elements of type telem. func (b Builder) ArrayAlloc(telem Type, n Expr) (ret Expr) { prog := b.Prog @@ -210,6 +242,7 @@ func (b Builder) ArrayAlloc(telem Type, n Expr) (ret Expr) { ret.Type = prog.Pointer(telem) return } +*/ // ----------------------------------------------------------------------------- diff --git a/ssa/stmt_builder.go b/ssa/stmt_builder.go index ac9550d0..f837c3fd 100644 --- a/ssa/stmt_builder.go +++ b/ssa/stmt_builder.go @@ -19,6 +19,7 @@ package ssa import ( "bytes" "fmt" + "go/token" "go/types" "log" "strings" @@ -248,6 +249,44 @@ func (b Builder) IfThen(cond Expr, then func()) { b.blk.last = blks[1].last } +/* TODO(xsw): +// For emits a for-loop instruction. +func (b Builder) For(cond func() Expr, loop func()) { + blks := b.Func.MakeBlocks(3) + b.Jump(blks[0]) + b.SetBlockEx(blks[0], AtEnd, false) + b.If(cond(), blks[1], blks[2]) + b.SetBlockEx(blks[1], AtEnd, false) + loop() + b.Jump(blks[0]) + b.SetBlockEx(blks[2], AtEnd, false) + b.blk.last = blks[2].last +} +*/ + +// Times emits a times-loop instruction. +func (b Builder) Times(n Expr, loop func(i Expr)) { + at := b.blk + blks := b.Func.MakeBlocks(3) + b.Jump(blks[0]) + b.SetBlockEx(blks[0], AtEnd, false) + typ := n.Type + phi := b.Phi(typ) + b.If(b.BinOp(token.LSS, phi.Expr, n), blks[1], blks[2]) + b.SetBlockEx(blks[1], AtEnd, false) + loop(phi.Expr) + post := b.BinOp(token.ADD, phi.Expr, b.Prog.IntVal(1, typ)) + b.Jump(blks[0]) + phi.AddIncoming(b, []BasicBlock{at, blks[1]}, func(i int, blk BasicBlock) Expr { + if i == 0 { + return b.Prog.IntVal(0, typ) + } + return post + }) + b.SetBlockEx(blks[2], AtEnd, false) + b.blk.last = blks[2].last +} + // ----------------------------------------------------------------------------- /* type caseStmt struct {