diff --git a/_demo/chandemo/chan.go b/_demo/_chandemo/chan.go similarity index 100% rename from _demo/chandemo/chan.go rename to _demo/_chandemo/chan.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 f0fe0950..4ea7f9cd 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/builtin_test.go b/cl/builtin_test.go index 5c479af5..a8916d49 100644 --- a/cl/builtin_test.go +++ b/cl/builtin_test.go @@ -143,6 +143,7 @@ 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("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..8e7d4a66 100644 --- a/ssa/memory.go +++ b/ssa/memory.go @@ -144,6 +144,13 @@ func (b Builder) Alloca(n Expr) (ret Expr) { return } +// 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 +162,29 @@ 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) { + s := b.Index(strs, i, nil) + 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,17 +219,15 @@ 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 } -*/ // ArrayAlloc allocates zero initialized space for an array of n elements of type telem. func (b Builder) ArrayAlloc(telem Type, n Expr) (ret Expr) { diff --git a/ssa/stmt_builder.go b/ssa/stmt_builder.go index ac9550d0..d6f1096f 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]) + b.SetBlockEx(blks[2], AtEnd, false) + b.blk.last = blks[2].last + phi.AddIncoming(b, []BasicBlock{at, blks[1]}, func(i int, blk BasicBlock) Expr { + if i == 0 { + return b.Prog.IntVal(0, typ) + } + return post + }) +} + // ----------------------------------------------------------------------------- /* type caseStmt struct {