diff --git a/c/c.go b/c/c.go index f035ff48..e557e829 100644 --- a/c/c.go +++ b/c/c.go @@ -63,6 +63,9 @@ func Unreachable() //go:linkname Malloc C.malloc func Malloc(size uintptr) Pointer +//go:linkname Free C.free +func Free(ptr Pointer) + //go:linkname Memcpy C.memcpy func Memcpy(dst, src Pointer, n uintptr) Pointer @@ -115,11 +118,20 @@ func Fwrite(data Pointer, size, count uintptr, fp FilePtr) uintptr //go:linkname Fputc C.fputc func Fputc(c Int, fp FilePtr) Int +//go:linkname Fputs C.fputs +func Fputs(s *Char, fp FilePtr) Int + +//go:linkname Fflush C.fflush +func Fflush(fp FilePtr) Int + // ----------------------------------------------------------------------------- //go:linkname Time C.time func Time(*int32) int32 +//go:linkname Usleep C.usleep +func Usleep(useconds Uint) Int + // ----------------------------------------------------------------------------- type Option struct { diff --git a/cl/_testgo/goroutine/in.go b/cl/_testgo/goroutine/in.go index 85def841..fd2ae48b 100644 --- a/cl/_testgo/goroutine/in.go +++ b/cl/_testgo/goroutine/in.go @@ -2,10 +2,10 @@ package main func main() { done := false - go func() { - println("Hello, World!") + go func(s string) { + println(s) done = true - }() + }("Hello, goroutine") for !done { print(".") } diff --git a/cl/_testgo/goroutine/out.ll b/cl/_testgo/goroutine/out.ll index 00c29c48..d6486190 100644 --- a/cl/_testgo/goroutine/out.ll +++ b/cl/_testgo/goroutine/out.ll @@ -6,8 +6,8 @@ source_filename = "main" @"main.init$guard" = global ptr null @__llgo_argc = global ptr null @__llgo_argv = global ptr null -@0 = private unnamed_addr constant [2 x i8] c".\00", align 1 -@1 = private unnamed_addr constant [14 x i8] c"Hello, World!\00", align 1 +@0 = private unnamed_addr constant [17 x i8] c"Hello, goroutine\00", align 1 +@1 = private unnamed_addr constant [2 x i8] c".\00", align 1 define void @main.init() { _llgo_0: @@ -39,51 +39,73 @@ _llgo_0: %7 = getelementptr inbounds { ptr, ptr }, ptr %5, i32 0, i32 1 store ptr %3, ptr %7, align 8 %8 = load { ptr, ptr }, ptr %5, align 8 - %9 = extractvalue { ptr, ptr } %8, 1 - %10 = extractvalue { ptr, ptr } %8, 0 - call void %10(ptr %9) + %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 @0, ptr %10, align 8 + %11 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %9, i32 0, i32 1 + store i64 16, ptr %11, align 4 + %12 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %9, align 8 + %13 = call ptr @malloc(i64 32) + %14 = getelementptr inbounds { { ptr, ptr }, %"github.com/goplus/llgo/internal/runtime.String" }, ptr %13, i32 0, i32 0 + store { ptr, ptr } %8, ptr %14, align 8 + %15 = getelementptr inbounds { { ptr, ptr }, %"github.com/goplus/llgo/internal/runtime.String" }, ptr %13, i32 0, i32 1 + store %"github.com/goplus/llgo/internal/runtime.String" %12, ptr %15, align 8 + %16 = alloca i8, i64 8, align 1 + %17 = call i32 @pthread_create(ptr %16, ptr null, ptr @"main._llgo_routine$1", ptr %13) br label %_llgo_3 _llgo_1: ; preds = %_llgo_3 - %11 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 - %12 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %11, i32 0, i32 0 - store ptr @0, ptr %12, align 8 - %13 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %11, i32 0, i32 1 - store i64 1, ptr %13, align 4 - %14 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %11, align 8 - call void @"github.com/goplus/llgo/internal/runtime.PrintString"(%"github.com/goplus/llgo/internal/runtime.String" %14) + %18 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 + %19 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %18, i32 0, i32 0 + store ptr @1, ptr %19, align 8 + %20 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %18, i32 0, i32 1 + store i64 1, ptr %20, align 4 + %21 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %18, align 8 + call void @"github.com/goplus/llgo/internal/runtime.PrintString"(%"github.com/goplus/llgo/internal/runtime.String" %21) br label %_llgo_3 _llgo_2: ; preds = %_llgo_3 ret i32 0 _llgo_3: ; preds = %_llgo_1, %_llgo_0 - %15 = load i1, ptr %2, align 1 - br i1 %15, label %_llgo_2, label %_llgo_1 + %22 = load i1, ptr %2, align 1 + br i1 %22, label %_llgo_2, label %_llgo_1 } declare void @"github.com/goplus/llgo/internal/runtime.init"() declare ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64) -define void @"main.main$1"(ptr %0) { +define void @"main.main$1"(ptr %0, %"github.com/goplus/llgo/internal/runtime.String" %1) { _llgo_0: - %1 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 - %2 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %1, i32 0, i32 0 - store ptr @1, ptr %2, align 8 - %3 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %1, i32 0, i32 1 - store i64 13, ptr %3, align 4 - %4 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %1, align 8 - call void @"github.com/goplus/llgo/internal/runtime.PrintString"(%"github.com/goplus/llgo/internal/runtime.String" %4) + call void @"github.com/goplus/llgo/internal/runtime.PrintString"(%"github.com/goplus/llgo/internal/runtime.String" %1) call void @"github.com/goplus/llgo/internal/runtime.PrintByte"(i8 10) - %5 = load { ptr }, ptr %0, align 8 - %6 = extractvalue { ptr } %5, 0 - store i1 true, ptr %6, align 1 + %2 = load { ptr }, ptr %0, align 8 + %3 = extractvalue { ptr } %2, 0 + store i1 true, ptr %3, align 1 ret void } declare ptr @"github.com/goplus/llgo/internal/runtime.AllocU"(i64) +declare ptr @malloc(i64) + +define ptr @"main._llgo_routine$1"(ptr %0) { +_llgo_0: + %1 = load { { ptr, ptr }, %"github.com/goplus/llgo/internal/runtime.String" }, ptr %0, align 8 + %2 = extractvalue { { ptr, ptr }, %"github.com/goplus/llgo/internal/runtime.String" } %1, 0 + %3 = extractvalue { { ptr, ptr }, %"github.com/goplus/llgo/internal/runtime.String" } %1, 1 + %4 = extractvalue { ptr, ptr } %2, 1 + %5 = extractvalue { ptr, ptr } %2, 0 + call void %5(ptr %4, %"github.com/goplus/llgo/internal/runtime.String" %3) + call void @free(ptr %0) + ret ptr null +} + +declare void @free(ptr) + +declare i32 @pthread_create(ptr, ptr, ptr, ptr) + declare void @"github.com/goplus/llgo/internal/runtime.PrintString"(%"github.com/goplus/llgo/internal/runtime.String") declare void @"github.com/goplus/llgo/internal/runtime.PrintByte"(i8) diff --git a/internal/abi/llgo_autogen.lla b/internal/abi/llgo_autogen.lla index 4d1e9792..747da4e5 100644 Binary files a/internal/abi/llgo_autogen.lla and b/internal/abi/llgo_autogen.lla differ diff --git a/internal/runtime/llgo_autogen.lla b/internal/runtime/llgo_autogen.lla index 8c7da6ae..6574534d 100644 Binary files a/internal/runtime/llgo_autogen.lla and b/internal/runtime/llgo_autogen.lla differ diff --git a/ssa/expr.go b/ssa/expr.go index fc50de9c..5c03955b 100644 --- a/ssa/expr.go +++ b/ssa/expr.go @@ -443,251 +443,6 @@ func (b Builder) UnOp(op token.Token, x Expr) (ret Expr) { // ----------------------------------------------------------------------------- -func checkExpr(v Expr, t types.Type, b Builder) Expr { - if t, ok := t.(*types.Struct); ok && isClosure(t) { - if v.kind != vkClosure { - return b.Pkg.closureStub(b, t, v) - } - } - return v -} - -func needsNegativeCheck(x Expr) bool { - if x.kind == vkSigned { - if rv := x.impl.IsAConstantInt(); !rv.IsNil() && rv.SExtValue() >= 0 { - return false - } - return true - } - return false -} - -func llvmParamsEx(data Expr, vals []Expr, params *types.Tuple, b Builder) (ret []llvm.Value) { - if data.IsNil() { - return llvmParams(0, vals, params, b) - } - ret = llvmParams(1, vals, params, b) - ret[0] = data.impl - return -} - -func llvmParams(base int, vals []Expr, params *types.Tuple, b Builder) (ret []llvm.Value) { - n := params.Len() - if n > 0 { - ret = make([]llvm.Value, len(vals)+base) - for idx, v := range vals { - i := base + idx - if i < n { - v = checkExpr(v, params.At(i).Type(), b) - } - ret[i] = v.impl - } - } - return -} - -func llvmFields(vals []Expr, t *types.Struct, b Builder) (ret []llvm.Value) { - n := t.NumFields() - if n > 0 { - ret = make([]llvm.Value, len(vals)) - for i, v := range vals { - if i < n { - v = checkExpr(v, t.Field(i).Type(), b) - } - ret[i] = v.impl - } - } - return -} - -// ----------------------------------------------------------------------------- - -// Advance returns the pointer ptr advanced by offset. -func (b Builder) Advance(ptr Expr, offset Expr) Expr { - if debugInstr { - log.Printf("Advance %v, %v\n", ptr.impl, offset.impl) - } - var elem llvm.Type - var prog = b.Prog - switch t := ptr.raw.Type.(type) { - case *types.Basic: // void - elem = prog.tyInt8() - default: - elem = prog.rawType(t.(*types.Pointer).Elem()).ll - } - ret := llvm.CreateGEP(b.impl, elem, 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 { - log.Printf("Load %v\n", ptr.impl) - } - if ptr.kind == vkPyVarRef { - return b.pyLoad(ptr) - } - telem := b.Prog.Elem(ptr.Type) - return Expr{llvm.CreateLoad(b.impl, telem.ll, ptr.impl), telem} -} - -// Store stores val at the pointer ptr. -func (b Builder) Store(ptr, val Expr) Builder { - raw := ptr.raw.Type - if debugInstr { - log.Printf("Store %v, %v, %v\n", raw, ptr.impl, val.impl) - } - val = checkExpr(val, raw.(*types.Pointer).Elem(), b) - b.impl.CreateStore(val.impl, ptr.impl) - return b -} - -func (b Builder) aggregateAlloc(t Type, flds ...llvm.Value) llvm.Value { - prog := b.Prog - size := prog.SizeOf(t) - ptr := b.InlineCall(b.Pkg.rtFunc("AllocU"), prog.IntVal(size, prog.Uintptr())).impl - tll := t.ll - impl := b.impl - for i, fld := range flds { - impl.CreateStore(fld, llvm.CreateStructGEP(impl, tll, ptr, i)) - } - return ptr -} - -// aggregateValue yields the value of the aggregate X with the fields -func (b Builder) aggregateValue(t Type, flds ...llvm.Value) Expr { - return Expr{aggregateValue(b.impl, t.ll, flds...), t} -} - -func aggregateValue(b llvm.Builder, tll llvm.Type, flds ...llvm.Value) llvm.Value { - ptr := llvm.CreateAlloca(b, tll) - for i, fld := range flds { - b.CreateStore(fld, llvm.CreateStructGEP(b, tll, ptr, i)) - } - return llvm.CreateLoad(b, tll, ptr) -} - -// The MakeClosure instruction yields a closure value whose code is -// Fn and whose free variables' values are supplied by Bindings. -// -// Type() returns a (possibly named) *types.Signature. -// -// Example printed form: -// -// t0 = make closure anon@1.2 [x y z] -// t1 = make closure bound$(main.I).add [i] -func (b Builder) MakeClosure(fn Expr, bindings []Expr) Expr { - if debugInstr { - log.Printf("MakeClosure %v, %v\n", fn, bindings) - } - prog := b.Prog - tfn := fn.Type - sig := tfn.raw.Type.(*types.Signature) - tctx := sig.Params().At(0).Type().Underlying().(*types.Pointer).Elem().(*types.Struct) - flds := llvmFields(bindings, tctx, b) - data := b.aggregateAlloc(prog.rawType(tctx), flds...) - return b.aggregateValue(prog.Closure(tfn), fn.impl, data) -} - -// ----------------------------------------------------------------------------- - -// The Alloc instruction reserves space for a variable of the given type, -// zero-initializes it, and yields its address. -// -// If heap is false, Alloc zero-initializes the same local variable in -// the call frame and returns its address; in this case the Alloc must -// be present in Function.Locals. We call this a "local" alloc. -// -// If heap is true, Alloc allocates a new zero-initialized variable -// each time the instruction is executed. We call this a "new" alloc. -// -// When Alloc is applied to a channel, map or slice type, it returns -// the address of an uninitialized (nil) reference of that kind; store -// the result of MakeSlice, MakeMap or MakeChan in that location to -// instantiate these types. -// -// Example printed form: -// -// t0 = local int -// t1 = new int -func (b Builder) Alloc(elem Type, heap bool) (ret Expr) { - if debugInstr { - log.Printf("Alloc %v, %v\n", elem.RawType(), heap) - } - prog := b.Prog - pkg := b.Pkg - size := SizeOf(prog, elem) - if heap { - ret = b.InlineCall(pkg.rtFunc("AllocZ"), size) - } else { - ret = Expr{llvm.CreateAlloca(b.impl, elem.ll), prog.VoidPtr()} - ret.impl = b.InlineCall(pkg.rtFunc("Zeroinit"), ret, size).impl - } - ret.Type = prog.Pointer(elem) - return -} - -// AllocU allocates uninitialized space for n*sizeof(elem) bytes. -func (b Builder) AllocU(elem Type, n ...int64) (ret Expr) { - prog := b.Prog - size := SizeOf(prog, elem, n...) - ret = b.InlineCall(b.Pkg.rtFunc("AllocU"), size) - ret.Type = prog.Pointer(elem) - return -} - -// AllocZ allocates zero initialized space for n bytes. -func (b Builder) AllocZ(n Expr) (ret Expr) { - return b.InlineCall(b.Pkg.rtFunc("AllocZ"), n) -} - -// Alloca allocates uninitialized space for n bytes. -func (b Builder) Alloca(n Expr) (ret Expr) { - if debugInstr { - log.Printf("Alloca %v\n", n.impl) - } - prog := b.Prog - telem := prog.tyInt8() - ret.impl = llvm.CreateArrayAlloca(b.impl, telem, n.impl) - ret.Type = prog.VoidPtr() - return -} - -// AllocaCStr allocates space for copy it from a Go string. -func (b Builder) AllocaCStr(gostr Expr) (ret Expr) { - if debugInstr { - log.Printf("AllocaCStr %v\n", gostr.impl) - } - n := b.StringLen(gostr) - n1 := b.BinOp(token.ADD, n, b.Prog.Val(1)) - cstr := b.Alloca(n1) - return b.InlineCall(b.Pkg.rtFunc("CStrCopy"), cstr, gostr) -} - -/* -// 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) - } - 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) { - prog := b.Prog - elemSize := SizeOf(prog, telem) - size := b.BinOp(token.MUL, n, elemSize) - ret.impl = b.AllocZ(size).impl - ret.Type = prog.Pointer(telem) - return -} - -// ----------------------------------------------------------------------------- - // The ChangeType instruction applies to X a value-preserving type // change to Type(). // @@ -876,6 +631,77 @@ func castPtr(b llvm.Builder, x llvm.Value, t llvm.Type) llvm.Value { // ----------------------------------------------------------------------------- +// The Range instruction yields an iterator over the domain and range +// of X, which must be a string or map. +// +// Elements are accessed via Next. +// +// Type() returns an opaque and degenerate "rangeIter" type. +// +// Pos() returns the ast.RangeStmt.For. +// +// Example printed form: +// +// t0 = range "hello":string +func (b Builder) Range(x Expr) Expr { + switch x.kind { + case vkString: + return b.InlineCall(b.Pkg.rtFunc("NewStringIter"), x) + } + panic("todo") +} + +// The Next instruction reads and advances the (map or string) +// iterator Iter and returns a 3-tuple value (ok, k, v). If the +// iterator is not exhausted, ok is true and k and v are the next +// elements of the domain and range, respectively. Otherwise ok is +// false and k and v are undefined. +// +// Components of the tuple are accessed using Extract. +// +// The IsString field distinguishes iterators over strings from those +// over maps, as the Type() alone is insufficient: consider +// map[int]rune. +// +// Type() returns a *types.Tuple for the triple (ok, k, v). +// The types of k and/or v may be types.Invalid. +// +// Example printed form: +// +// t1 = next t0 +func (b Builder) Next(iter Expr, isString bool) (ret Expr) { + if isString { + return b.InlineCall(b.Pkg.rtFunc("StringIterNext"), iter) + } + panic("todo") +} + +// ----------------------------------------------------------------------------- + +// The MakeClosure instruction yields a closure value whose code is +// Fn and whose free variables' values are supplied by Bindings. +// +// Type() returns a (possibly named) *types.Signature. +// +// Example printed form: +// +// t0 = make closure anon@1.2 [x y z] +// t1 = make closure bound$(main.I).add [i] +func (b Builder) MakeClosure(fn Expr, bindings []Expr) Expr { + if debugInstr { + log.Printf("MakeClosure %v, %v\n", fn, bindings) + } + prog := b.Prog + tfn := fn.Type + sig := tfn.raw.Type.(*types.Signature) + tctx := sig.Params().At(0).Type().Underlying().(*types.Pointer).Elem().(*types.Struct) + flds := llvmFields(bindings, tctx, b) + data := b.aggregateAllocU(prog.rawType(tctx), flds...) + return b.aggregateValue(prog.Closure(tfn), fn.impl, data) +} + +// ----------------------------------------------------------------------------- + // TODO(xsw): make inline call func (b Builder) InlineCall(fn Expr, args ...Expr) (ret Expr) { return b.Call(fn, args...) @@ -958,51 +784,6 @@ func (b Builder) Do(da DoAction, fn Expr, args ...Expr) (ret Expr) { return } -// The Range instruction yields an iterator over the domain and range -// of X, which must be a string or map. -// -// Elements are accessed via Next. -// -// Type() returns an opaque and degenerate "rangeIter" type. -// -// Pos() returns the ast.RangeStmt.For. -// -// Example printed form: -// -// t0 = range "hello":string -func (b Builder) Range(x Expr) Expr { - switch x.kind { - case vkString: - return b.InlineCall(b.Pkg.rtFunc("NewStringIter"), x) - } - panic("todo") -} - -// The Next instruction reads and advances the (map or string) -// iterator Iter and returns a 3-tuple value (ok, k, v). If the -// iterator is not exhausted, ok is true and k and v are the next -// elements of the domain and range, respectively. Otherwise ok is -// false and k and v are undefined. -// -// Components of the tuple are accessed using Extract. -// -// The IsString field distinguishes iterators over strings from those -// over maps, as the Type() alone is insufficient: consider -// map[int]rune. -// -// Type() returns a *types.Tuple for the triple (ok, k, v). -// The types of k and/or v may be types.Invalid. -// -// Example printed form: -// -// t1 = next t0 -func (b Builder) Next(iter Expr, isString bool) (ret Expr) { - if isString { - return b.InlineCall(b.Pkg.rtFunc("StringIterNext"), iter) - } - panic("todo") -} - // A Builtin represents a specific use of a built-in function, e.g. len. // // Builtins are immutable values. Builtins do not have addresses. @@ -1139,3 +920,62 @@ func (b Builder) PrintEx(ln bool, args ...Expr) (ret Expr) { } // ----------------------------------------------------------------------------- + +func checkExpr(v Expr, t types.Type, b Builder) Expr { + if t, ok := t.(*types.Struct); ok && isClosure(t) { + if v.kind != vkClosure { + return b.Pkg.closureStub(b, t, v) + } + } + return v +} + +func needsNegativeCheck(x Expr) bool { + if x.kind == vkSigned { + if rv := x.impl.IsAConstantInt(); !rv.IsNil() && rv.SExtValue() >= 0 { + return false + } + return true + } + return false +} + +func llvmParamsEx(data Expr, vals []Expr, params *types.Tuple, b Builder) (ret []llvm.Value) { + if data.IsNil() { + return llvmParams(0, vals, params, b) + } + ret = llvmParams(1, vals, params, b) + ret[0] = data.impl + return +} + +func llvmParams(base int, vals []Expr, params *types.Tuple, b Builder) (ret []llvm.Value) { + n := params.Len() + if n > 0 { + ret = make([]llvm.Value, len(vals)+base) + for idx, v := range vals { + i := base + idx + if i < n { + v = checkExpr(v, params.At(i).Type(), b) + } + ret[i] = v.impl + } + } + return +} + +func llvmFields(vals []Expr, t *types.Struct, b Builder) (ret []llvm.Value) { + n := t.NumFields() + if n > 0 { + ret = make([]llvm.Value, len(vals)) + for i, v := range vals { + if i < n { + v = checkExpr(v, t.Field(i).Type(), b) + } + ret[i] = v.impl + } + } + return +} + +// ----------------------------------------------------------------------------- diff --git a/ssa/goroutine.go b/ssa/goroutine.go new file mode 100644 index 00000000..e050632a --- /dev/null +++ b/ssa/goroutine.go @@ -0,0 +1,114 @@ +/* + * 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 ssa + +import ( + "go/token" + "go/types" + "strconv" + + "github.com/goplus/llvm" +) + +// ----------------------------------------------------------------------------- + +// func(c.Pointer) c.Pointer +func (p Program) tyRoutine() *types.Signature { + if p.routineTy == nil { + paramPtr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type) + params := types.NewTuple(paramPtr) + p.routineTy = types.NewSignatureType(nil, nil, nil, params, params, false) + } + return p.routineTy +} + +// func(pthread *Thread, attr *Attr, routine func(c.Pointer) c.Pointer, arg c.Pointer) c.Int +func (p Program) tyPthreadCreate() *types.Signature { + if p.createThdTy == nil { + paramPPtr := types.NewParam(token.NoPos, nil, "", p.VoidPtrPtr().raw.Type) + paramPtr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type) + paramRoutine := types.NewParam(token.NoPos, nil, "", p.tyRoutine()) + paramCInt := types.NewParam(token.NoPos, nil, "", p.CInt().raw.Type) + params := types.NewTuple(paramPPtr, paramPtr, paramRoutine, paramPtr) + results := types.NewTuple(paramCInt) + p.createThdTy = types.NewSignatureType(nil, nil, nil, params, results, false) + } + return p.createThdTy +} + +func (b Builder) createThread(pp, attr, routine, arg Expr) Expr { + fn := b.Pkg.cFunc("pthread_create", b.Prog.tyPthreadCreate()) + return b.Call(fn, pp, attr, routine, arg) +} + +// ----------------------------------------------------------------------------- + +// The Go instruction creates a new goroutine and calls the specified +// function within it. +// +// Example printed form: +// +// go println(t0, t1) +// go t3() +// go invoke t5.Println(...t6) +func (b Builder) Go(fn Expr, args ...Expr) { + if debugInstr { + logCall("Go", fn, args) + } + + prog := b.Prog + pkg := b.Pkg + + typs := make([]Type, len(args)+1) + flds := make([]llvm.Value, len(args)+1) + typs[0] = fn.Type + flds[0] = fn.impl + for i, arg := range args { + typs[i+1] = arg.Type + flds[i+1] = arg.impl + } + t := prog.Struct(typs...) + voidPtr := prog.VoidPtr() + data := Expr{b.aggregateMalloc(t, flds...), voidPtr} + size := prog.SizeOf(voidPtr) + pthd := b.Alloca(prog.IntVal(uint64(size), prog.Uintptr())) + b.createThread(pthd, prog.Null(voidPtr), pkg.routine(t, len(args)), data) +} + +func (p Package) routineName() string { + p.iRoutine++ + return p.Path() + "._llgo_routine$" + strconv.Itoa(p.iRoutine) +} + +func (p Package) routine(t Type, n int) Expr { + prog := p.Prog + routine := p.NewFunc(p.routineName(), prog.tyRoutine(), InC) + b := routine.MakeBody(1) + param := routine.Param(0) + data := Expr{llvm.CreateLoad(b.impl, t.ll, param.impl), t} + args := make([]Expr, n) + fn := b.getField(data, 0) + for i := 0; i < n; i++ { + args[i] = b.getField(data, i+1) + } + b.Call(fn, args...) + b.free(param) + b.Return(prog.Null(prog.VoidPtr())) + return routine.Expr +} + +// ----------------------------------------------------------------------------- diff --git a/ssa/memory.go b/ssa/memory.go new file mode 100644 index 00000000..df7b4908 --- /dev/null +++ b/ssa/memory.go @@ -0,0 +1,235 @@ +/* + * 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 ssa + +import ( + "go/token" + "go/types" + "log" + + "github.com/goplus/llvm" +) + +// ----------------------------------------------------------------------------- + +// Advance returns the pointer ptr advanced by offset. +func (b Builder) Advance(ptr Expr, offset Expr) Expr { + if debugInstr { + log.Printf("Advance %v, %v\n", ptr.impl, offset.impl) + } + var elem llvm.Type + var prog = b.Prog + switch t := ptr.raw.Type.(type) { + case *types.Basic: // void + elem = prog.tyInt8() + default: + elem = prog.rawType(t.(*types.Pointer).Elem()).ll + } + ret := llvm.CreateGEP(b.impl, elem, 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 { + log.Printf("Load %v\n", ptr.impl) + } + if ptr.kind == vkPyVarRef { + return b.pyLoad(ptr) + } + telem := b.Prog.Elem(ptr.Type) + return Expr{llvm.CreateLoad(b.impl, telem.ll, ptr.impl), telem} +} + +// Store stores val at the pointer ptr. +func (b Builder) Store(ptr, val Expr) Builder { + raw := ptr.raw.Type + if debugInstr { + log.Printf("Store %v, %v, %v\n", raw, ptr.impl, val.impl) + } + val = checkExpr(val, raw.(*types.Pointer).Elem(), b) + b.impl.CreateStore(val.impl, ptr.impl) + return b +} + +func (b Builder) aggregateAllocU(t Type, flds ...llvm.Value) llvm.Value { + prog := b.Prog + size := prog.SizeOf(t) + ptr := b.allocUninited(prog.IntVal(size, prog.Uintptr())).impl + aggregateInit(b.impl, ptr, t.ll, flds...) + return ptr +} + +func (b Builder) aggregateMalloc(t Type, flds ...llvm.Value) llvm.Value { + prog := b.Prog + size := prog.SizeOf(t) + ptr := b.malloc(prog.IntVal(size, prog.Uintptr())).impl + aggregateInit(b.impl, ptr, t.ll, flds...) + return ptr +} + +// aggregateValue yields the value of the aggregate X with the fields +func (b Builder) aggregateValue(t Type, flds ...llvm.Value) Expr { + return Expr{aggregateValue(b.impl, t.ll, flds...), t} +} + +func aggregateValue(b llvm.Builder, tll llvm.Type, flds ...llvm.Value) llvm.Value { + ptr := llvm.CreateAlloca(b, tll) + aggregateInit(b, ptr, tll, flds...) + return llvm.CreateLoad(b, tll, ptr) +} + +func aggregateInit(b llvm.Builder, ptr llvm.Value, tll llvm.Type, flds ...llvm.Value) { + for i, fld := range flds { + b.CreateStore(fld, llvm.CreateStructGEP(b, tll, ptr, i)) + } +} + +// ----------------------------------------------------------------------------- + +// The Alloc instruction reserves space for a variable of the given type, +// zero-initializes it, and yields its address. +// +// If heap is false, Alloc zero-initializes the same local variable in +// the call frame and returns its address; in this case the Alloc must +// be present in Function.Locals. We call this a "local" alloc. +// +// If heap is true, Alloc allocates a new zero-initialized variable +// each time the instruction is executed. We call this a "new" alloc. +// +// When Alloc is applied to a channel, map or slice type, it returns +// the address of an uninitialized (nil) reference of that kind; store +// the result of MakeSlice, MakeMap or MakeChan in that location to +// instantiate these types. +// +// Example printed form: +// +// t0 = local int +// t1 = new int +func (b Builder) Alloc(elem Type, heap bool) (ret Expr) { + if debugInstr { + log.Printf("Alloc %v, %v\n", elem.RawType(), heap) + } + prog := b.Prog + pkg := b.Pkg + size := SizeOf(prog, elem) + if heap { + ret = b.InlineCall(pkg.rtFunc("AllocZ"), size) + } else { + ret = Expr{llvm.CreateAlloca(b.impl, elem.ll), prog.VoidPtr()} + ret.impl = b.InlineCall(pkg.rtFunc("Zeroinit"), ret, size).impl + } + ret.Type = prog.Pointer(elem) + return +} + +// AllocU allocates uninitialized space for n*sizeof(elem) bytes. +func (b Builder) AllocU(elem Type, n ...int64) (ret Expr) { + prog := b.Prog + size := SizeOf(prog, elem, n...) + return Expr{b.allocUninited(size).impl, prog.Pointer(elem)} +} + +func (b Builder) allocUninited(size Expr) (ret Expr) { + return b.InlineCall(b.Pkg.rtFunc("AllocU"), size) +} + +// AllocZ allocates zero initialized space for n bytes. +func (b Builder) AllocZ(n Expr) (ret Expr) { + return b.InlineCall(b.Pkg.rtFunc("AllocZ"), n) +} + +// Alloca allocates uninitialized space for n bytes. +func (b Builder) Alloca(n Expr) (ret Expr) { + if debugInstr { + log.Printf("Alloca %v\n", n.impl) + } + prog := b.Prog + telem := prog.tyInt8() + ret.impl = llvm.CreateArrayAlloca(b.impl, telem, n.impl) + ret.Type = prog.VoidPtr() + return +} + +// AllocaCStr allocates space for copy it from a Go string. +func (b Builder) AllocaCStr(gostr Expr) (ret Expr) { + if debugInstr { + log.Printf("AllocaCStr %v\n", gostr.impl) + } + n := b.StringLen(gostr) + n1 := b.BinOp(token.ADD, n, b.Prog.Val(1)) + cstr := b.Alloca(n1) + return b.InlineCall(b.Pkg.rtFunc("CStrCopy"), cstr, gostr) +} + +// ----------------------------------------------------------------------------- + +func (p Program) tyMalloc() *types.Signature { + if p.mallocTy == nil { + paramSize := types.NewParam(token.NoPos, nil, "", p.Uintptr().raw.Type) + paramPtr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type) + params := types.NewTuple(paramSize) + results := types.NewTuple(paramPtr) + p.mallocTy = types.NewSignatureType(nil, nil, nil, params, results, false) + } + return p.mallocTy +} + +func (p Program) tyFree() *types.Signature { + if p.freeTy == nil { + paramPtr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type) + params := types.NewTuple(paramPtr) + p.freeTy = types.NewSignatureType(nil, nil, nil, params, nil, false) + } + return p.freeTy +} + +func (b Builder) malloc(size Expr) Expr { + fn := b.Pkg.cFunc("malloc", b.Prog.tyMalloc()) + return b.Call(fn, size) +} + +func (b Builder) free(ptr Expr) Expr { + fn := b.Pkg.cFunc("free", b.Prog.tyFree()) + return b.Call(fn, ptr) +} + +// ----------------------------------------------------------------------------- + +/* +// 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) + } + 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) { + prog := b.Prog + elemSize := SizeOf(prog, telem) + size := b.BinOp(token.MUL, n, elemSize) + ret.impl = b.AllocZ(size).impl + ret.Type = prog.Pointer(telem) + return +} + +// ----------------------------------------------------------------------------- diff --git a/ssa/package.go b/ssa/package.go index 8ddd4865..a81deef7 100644 --- a/ssa/package.go +++ b/ssa/package.go @@ -163,6 +163,12 @@ type aProgram struct { loadPyModS *types.Signature getAttrStr *types.Signature + mallocTy *types.Signature + freeTy *types.Signature + + createThdTy *types.Signature + routineTy *types.Signature + paramObjPtr_ *types.Var ptrSize int @@ -511,6 +517,8 @@ func (p Program) Uint64() Type { // and unspecified other things too. type aPackage struct { mod llvm.Module + abi abi.Builder + abiTypes vars map[string]Global fns map[string]Function @@ -519,8 +527,7 @@ type aPackage struct { pymods map[string]Global Prog Program - abi abi.Builder - abiTypes + iRoutine int } type Package = *aPackage @@ -532,6 +539,10 @@ func (p Package) rtFunc(fnName string) Expr { return p.NewFunc(name, sig, InGo).Expr } +func (p Package) cFunc(fullName string, sig *types.Signature) Expr { + return p.NewFunc(fullName, sig, InC).Expr +} + func (p Package) pyFunc(fullName string, sig *types.Signature) Expr { p.Prog.NeedPyInit = true return p.NewFunc(fullName, sig, InC).Expr diff --git a/ssa/stmt_builder.go b/ssa/stmt_builder.go index 7ed0a746..92fcd37f 100644 --- a/ssa/stmt_builder.go +++ b/ssa/stmt_builder.go @@ -142,21 +142,6 @@ func (b Builder) Unreachable() { b.impl.CreateUnreachable() } -// The Go instruction creates a new goroutine and calls the specified -// function within it. -// -// Example printed form: -// -// go println(t0, t1) -// go t3() -// go invoke t5.Println(...t6) -func (b Builder) Go(fn Expr, args ...Expr) { - if debugInstr { - logCall("Go", fn, args) - } - b.Call(fn, args...) -} - // Return emits a return instruction. func (b Builder) Return(results ...Expr) { if debugInstr {