diff --git a/cl/_testrt/concat/in.go b/cl/_testrt/concat/in.go index 5328026a..811a0c8d 100644 --- a/cl/_testrt/concat/in.go +++ b/cl/_testrt/concat/in.go @@ -4,6 +4,14 @@ import ( "github.com/goplus/llgo/internal/runtime/c" ) -func main() { - c.Fprintf(c.Stderr, c.Str("Hello %d\n"), 100) +func concat(args ...string) (ret string) { + for _, v := range args { + ret += v + } + return +} + +func main() { + result := concat("Hello", " ", "World") + c.Fprintf(c.Stderr, c.Str("Hello %s\n"), c.AllocaCStr(result)) } diff --git a/cl/_testrt/concat/out.ll b/cl/_testrt/concat/out.ll index ee0cd9d7..06e49967 100644 --- a/cl/_testrt/concat/out.ll +++ b/cl/_testrt/concat/out.ll @@ -1,9 +1,40 @@ ; 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 ptr null +@0 = private unnamed_addr constant [1 x i8] zeroinitializer, align 1 +@1 = private unnamed_addr constant [6 x i8] c"Hello\00", align 1 +@2 = private unnamed_addr constant [2 x i8] c" \00", align 1 +@3 = private unnamed_addr constant [6 x i8] c"World\00", align 1 @__stderrp = external global ptr -@0 = private unnamed_addr constant [10 x i8] c"Hello %d\0A\00", align 1 +@4 = private unnamed_addr constant [10 x i8] c"Hello %s\0A\00", align 1 + +define %"github.com/goplus/llgo/internal/runtime.String" @main.concat(%"github.com/goplus/llgo/internal/runtime.Slice" %0) { +_llgo_0: + %1 = call i64 @"github.com/goplus/llgo/internal/runtime.SliceLen"(%"github.com/goplus/llgo/internal/runtime.Slice" %0) + br label %_llgo_1 + +_llgo_1: ; preds = %_llgo_2, %_llgo_0 + %2 = phi %"github.com/goplus/llgo/internal/runtime.String" [ %10, %_llgo_0 ], [ %9, %_llgo_2 ] + %3 = phi i64 [ -1, %_llgo_0 ], [ %4, %_llgo_2 ] + %4 = add i64 %3, 1 + %5 = icmp slt i64 %4, %1 + br i1 %5, label %_llgo_2, label %_llgo_3 + +_llgo_2: ; preds = %_llgo_1 + %6 = call ptr @"github.com/goplus/llgo/internal/runtime.SliceData"(%"github.com/goplus/llgo/internal/runtime.Slice" %0) + %7 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %6, i64 %4 + %8 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %7, align 8 + %9 = call %"github.com/goplus/llgo/internal/runtime.String" @"github.com/goplus/llgo/internal/runtime.StringCat"(%"github.com/goplus/llgo/internal/runtime.String" %2, %"github.com/goplus/llgo/internal/runtime.String" %8) + br label %_llgo_1 + +_llgo_3: ; preds = %_llgo_1 + ret %"github.com/goplus/llgo/internal/runtime.String" %2 + %10 = call %"github.com/goplus/llgo/internal/runtime.String" @"github.com/goplus/llgo/internal/runtime.NewString"(ptr @0, i64 0) +} define void @main.init() { _llgo_0: @@ -22,11 +53,43 @@ define void @main() { _llgo_0: call void @"github.com/goplus/llgo/internal/runtime.init"() call void @main.init() - %0 = load ptr, ptr @__stderrp, align 8 - %1 = call i32 (ptr, ptr, ...) @fprintf(ptr %0, ptr @0, i64 100) + %0 = call ptr @"github.com/goplus/llgo/internal/runtime.Alloc"(i64 48) + %1 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %0, i64 0 + %2 = call %"github.com/goplus/llgo/internal/runtime.String" @"github.com/goplus/llgo/internal/runtime.NewString"(ptr @1, i64 5) + store %"github.com/goplus/llgo/internal/runtime.String" %2, ptr %1, align 8 + %3 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %0, i64 1 + %4 = call %"github.com/goplus/llgo/internal/runtime.String" @"github.com/goplus/llgo/internal/runtime.NewString"(ptr @2, i64 1) + store %"github.com/goplus/llgo/internal/runtime.String" %4, ptr %3, align 8 + %5 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %0, i64 2 + %6 = call %"github.com/goplus/llgo/internal/runtime.String" @"github.com/goplus/llgo/internal/runtime.NewString"(ptr @3, i64 5) + store %"github.com/goplus/llgo/internal/runtime.String" %6, ptr %5, align 8 + %7 = call %"github.com/goplus/llgo/internal/runtime.Slice" @"github.com/goplus/llgo/internal/runtime.NewSlice"(ptr %0, i64 3, i64 3) + %8 = call %"github.com/goplus/llgo/internal/runtime.String" @main.concat(%"github.com/goplus/llgo/internal/runtime.Slice" %7) + %9 = load ptr, ptr @__stderrp, align 8 + %10 = call i64 @"github.com/goplus/llgo/internal/runtime.StringLen"(%"github.com/goplus/llgo/internal/runtime.String" %8) + %11 = add i64 %10, 1 + %12 = alloca i8, i64 %11, align 1 + %13 = call ptr @"github.com/goplus/llgo/internal/runtime.CStrCopy"(ptr %12, %"github.com/goplus/llgo/internal/runtime.String" %8) + %14 = call i32 (ptr, ptr, ...) @fprintf(ptr %9, ptr @4, ptr %13) ret void } +declare i64 @"github.com/goplus/llgo/internal/runtime.SliceLen"(%"github.com/goplus/llgo/internal/runtime.Slice") + +declare ptr @"github.com/goplus/llgo/internal/runtime.SliceData"(%"github.com/goplus/llgo/internal/runtime.Slice") + +declare %"github.com/goplus/llgo/internal/runtime.String" @"github.com/goplus/llgo/internal/runtime.StringCat"(%"github.com/goplus/llgo/internal/runtime.Slice") + +declare %"github.com/goplus/llgo/internal/runtime.String" @"github.com/goplus/llgo/internal/runtime.NewString"(ptr, i64) + declare void @"github.com/goplus/llgo/internal/runtime.init"() +declare ptr @"github.com/goplus/llgo/internal/runtime.Alloc"(i64) + +declare %"github.com/goplus/llgo/internal/runtime.Slice" @"github.com/goplus/llgo/internal/runtime.NewSlice"(ptr, i64, i64) + +declare i64 @"github.com/goplus/llgo/internal/runtime.StringLen"(%"github.com/goplus/llgo/internal/runtime.Slice") + +declare ptr @"github.com/goplus/llgo/internal/runtime.CStrCopy"(ptr, %"github.com/goplus/llgo/internal/runtime.String") + declare i32 @fprintf(ptr, ptr, ...) diff --git a/cl/builtin_test.go b/cl/builtin_test.go index 66d0e839..da94ef47 100644 --- a/cl/builtin_test.go +++ b/cl/builtin_test.go @@ -25,14 +25,24 @@ import ( "golang.org/x/tools/go/ssa" ) +func TestErrAdvance(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Fatal("advance: no error?") + } + }() + var ctx context + ctx.advance(nil, nil) +} + func TestErrAlloca(t *testing.T) { defer func() { if r := recover(); r == nil { t.Fatal("alloca: no error?") } }() - var ctz context - ctz.alloca(nil, nil) + var ctx context + ctx.alloca(nil, nil) } func TestCStrNoArgs(t *testing.T) { diff --git a/cl/compile.go b/cl/compile.go index c374a5d5..1d09b2fc 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -300,6 +300,15 @@ func cstr(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { panic("cstr(): invalid arguments") } +func (p *context) advance(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { + if len(args) == 2 { + ptr := p.compileValue(b, args[0]) + offset := p.compileValue(b, args[1]) + return b.Advance(ptr, offset) + } + panic("advance(p ptr, offset int): invalid arguments") +} + // func alloca(size uintptr) unsafe.Pointer func (p *context) alloca(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { if len(args) == 1 { @@ -355,6 +364,8 @@ func (p *context) compileInstrOrValue(b llssa.Builder, iv instrOrValue, asValue ret = b.Call(fn.Expr, args...) case llgoCstr: ret = cstr(b, call.Args) + case llgoAdvance: + ret = p.advance(b, call.Args) case llgoAlloca: ret = p.alloca(b, call.Args) case llgoAllocaCStr: diff --git a/cl/import.go b/cl/import.go index bdfa1050..e02e5d34 100644 --- a/cl/import.go +++ b/cl/import.go @@ -200,6 +200,7 @@ const ( llgoCstr = llgoInstrBase + 1 llgoAlloca = llgoInstrBase + 2 llgoAllocaCStr = llgoInstrBase + 3 + llgoAdvance = llgoInstrBase + 4 ) func (p *context) funcName(pkg *types.Package, fn *ssa.Function, ignore bool) (string, int) { @@ -237,6 +238,8 @@ func (p *context) funcOf(fn *ssa.Function) (ret llssa.Function, ftype int) { switch name { case "cstr": ftype = llgoCstr + case "advance": + ftype = llgoAdvance case "alloca": ftype = llgoAlloca case "allocaCStr": diff --git a/internal/runtime/c/c.go b/internal/runtime/c/c.go index 05bd8ec6..ca111468 100644 --- a/internal/runtime/c/c.go +++ b/internal/runtime/c/c.go @@ -42,6 +42,9 @@ var Stderr FilePtr //go:linkname Str llgo.cstr func Str(string) *Char +//go:linkname Advance llgo.advance +func Advance(ptr Pointer, offset int) Pointer + //go:linkname Alloca llgo.alloca func Alloca(size uintptr) Pointer diff --git a/internal/runtime/llgo_autogen.ll b/internal/runtime/llgo_autogen.ll index a51d9cbe..cdb2cbde 100644 --- a/internal/runtime/llgo_autogen.ll +++ b/internal/runtime/llgo_autogen.ll @@ -265,6 +265,67 @@ _llgo_0: ret i64 %3 } +define %"github.com/goplus/llgo/internal/runtime.String" @"github.com/goplus/llgo/internal/runtime.StringCat"(%"github.com/goplus/llgo/internal/runtime.Slice" %0) { +_llgo_0: + %1 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 + %2 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 + %3 = call i64 @"github.com/goplus/llgo/internal/runtime.SliceLen"(%"github.com/goplus/llgo/internal/runtime.Slice" %0) + br label %_llgo_1 + +_llgo_1: ; preds = %_llgo_2, %_llgo_0 + %4 = phi i64 [ 0, %_llgo_0 ], [ %13, %_llgo_2 ] + %5 = phi i64 [ -1, %_llgo_0 ], [ %6, %_llgo_2 ] + %6 = add i64 %5, 1 + %7 = icmp slt i64 %6, %3 + br i1 %7, label %_llgo_2, label %_llgo_3 + +_llgo_2: ; preds = %_llgo_1 + %8 = call ptr @"github.com/goplus/llgo/internal/runtime.SliceData"(%"github.com/goplus/llgo/internal/runtime.Slice" %0) + %9 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %8, i64 %6 + %10 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %9, align 8 + store %"github.com/goplus/llgo/internal/runtime.String" %10, ptr %2, align 8 + %11 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %2, i32 0, i32 1 + %12 = load i64, ptr %11, align 4 + %13 = add i64 %4, %12 + br label %_llgo_1 + +_llgo_3: ; preds = %_llgo_1 + %14 = call ptr @"github.com/goplus/llgo/internal/runtime.Alloc"(i64 %4) + %15 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %1, i32 0, i32 0 + store ptr %14, ptr %15, align 8 + %16 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %1, i32 0, i32 1 + store i64 %4, ptr %16, align 4 + %17 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 + %18 = call i64 @"github.com/goplus/llgo/internal/runtime.SliceLen"(%"github.com/goplus/llgo/internal/runtime.Slice" %0) + br label %_llgo_4 + +_llgo_4: ; preds = %_llgo_5, %_llgo_3 + %19 = phi ptr [ %14, %_llgo_3 ], [ %33, %_llgo_5 ] + %20 = phi i64 [ -1, %_llgo_3 ], [ %21, %_llgo_5 ] + %21 = add i64 %20, 1 + %22 = icmp slt i64 %21, %18 + br i1 %22, label %_llgo_5, label %_llgo_6 + +_llgo_5: ; preds = %_llgo_4 + %23 = call ptr @"github.com/goplus/llgo/internal/runtime.SliceData"(%"github.com/goplus/llgo/internal/runtime.Slice" %0) + %24 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %23, i64 %21 + %25 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %24, align 8 + store %"github.com/goplus/llgo/internal/runtime.String" %25, ptr %17, align 8 + %26 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %17, i32 0, i32 0 + %27 = load ptr, ptr %26, align 8 + %28 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %17, i32 0, i32 1 + %29 = load i64, ptr %28, align 4 + %30 = call ptr @memcpy(ptr %19, ptr %27, i64 %29) + %31 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %17, i32 0, i32 1 + %32 = load i64, ptr %31, align 4 + %33 = getelementptr i8, ptr %19, i64 %32 + br label %_llgo_4 + +_llgo_6: ; preds = %_llgo_4 + %34 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %1, align 8 + ret %"github.com/goplus/llgo/internal/runtime.String" %34 +} + define ptr @"github.com/goplus/llgo/internal/runtime.StringData"(%"github.com/goplus/llgo/internal/runtime.String" %0) { _llgo_0: %1 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 diff --git a/internal/runtime/z_string.go b/internal/runtime/z_string.go index 1ee8109a..e362da23 100644 --- a/internal/runtime/z_string.go +++ b/internal/runtime/z_string.go @@ -55,6 +55,22 @@ func StringData(s String) unsafe.Pointer { return s.data } +// StringCat concatenates strings. +func StringCat(args ...String) (ret String) { + n := 0 + for _, v := range args { + n += v.len + } + dest := Alloc(uintptr(n)) + ret.data = dest + ret.len = n + for _, v := range args { + c.Memcpy(dest, v.data, uintptr(v.len)) + dest = c.Advance(dest, v.len) + } + return +} + // ----------------------------------------------------------------------------- // CStrCopy copies a Go string to a C string buffer and returns it. diff --git a/ssa/expr.go b/ssa/expr.go index c19ee41d..e19c7a12 100644 --- a/ssa/expr.go +++ b/ssa/expr.go @@ -268,12 +268,17 @@ func (b Builder) BinOp(op token.Token, x, y Expr) Expr { case isMathOp(op): // op: + - * / % kind := x.kind switch kind { - case vkString, vkComplex: - panic("todo") - } - idx := mathOpIdx(op, kind) - if llop := mathOpToLLVM[idx]; llop != 0 { - return Expr{llvm.CreateBinOp(b.impl, llop, x.impl, y.impl), x.Type} + case vkString: + if op == token.ADD { + pkg := b.fn.pkg + return b.InlineCall(pkg.rtFunc("StringCat"), x, y) + } + case vkComplex: + default: + idx := mathOpIdx(op, kind) + if llop := mathOpToLLVM[idx]; llop != 0 { + return Expr{llvm.CreateBinOp(b.impl, llop, x.impl, y.impl), x.Type} + } } case isLogicOp(op): // op: & | ^ << >> &^ if op == token.AND_NOT { @@ -359,6 +364,15 @@ func (b Builder) Phi(t Type) Phi { // ----------------------------------------------------------------------------- +// Advance returns the pointer ptr advanced by offset bytes. +func (b Builder) Advance(ptr Expr, offset Expr) Expr { + if debugInstr { + log.Printf("Advance %v, %v\n", ptr.impl, offset.impl) + } + ret := llvm.CreateGEP(b.impl, b.Prog.tyInt8(), ptr.impl, []llvm.Value{offset.impl}) + return Expr{ret, ptr.Type} +} + // Load returns the value at the pointer ptr. func (b Builder) Load(ptr Expr) Expr { if debugInstr {