Merge pull request #75 from xushiwei/q

cl: support llgo.cstr
This commit is contained in:
xushiwei
2024-04-29 19:01:01 +08:00
committed by GitHub
14 changed files with 323 additions and 43 deletions

14
cl/_testcgo/alloca/in.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import (
"unsafe"
"github.com/goplus/llgo/internal/runtime/c"
)
func main() {
s := c.String("Hi\n")
s2 := c.Alloca(4)
c.Memcpy(s2, unsafe.Pointer(s), 4)
c.Printf(c.String("%s"), s)
}

36
cl/_testcgo/alloca/out.ll Normal file
View File

@@ -0,0 +1,36 @@
; ModuleID = 'main'
source_filename = "main"
%"github.com/goplus/llgo/internal/runtime.String" = type { ptr, i64 }
@"main.init$guard" = global ptr null
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 void @main() {
_llgo_0:
call void @main.init()
%0 = call ptr @"github.com/goplus/llgo/internal/runtime/c.String"([4 x i8] c"Hi\0A\00")
%1 = alloca i8, i64 4, align 1
%2 = call ptr @memcpy(ptr %1, ptr %0, i64 4)
%3 = call ptr @"github.com/goplus/llgo/internal/runtime/c.String"([3 x i8] c"%s\00")
%4 = call i32 (ptr, ...) @printf(ptr %3, ptr %0)
ret void
}
declare ptr @"github.com/goplus/llgo/internal/runtime/c.String"(%"github.com/goplus/llgo/internal/runtime.String")
declare ptr @memcpy(ptr, ptr, i64)
declare i32 @printf(ptr, ...)

13
cl/_testcgo/cstr/in.go Normal file
View File

@@ -0,0 +1,13 @@
package main
import _ "unsafe"
//go:linkname cstr llgo.cstr
func cstr(string) *int8
//go:linkname printf C.printf
func printf(format *int8, __llgo_va_list ...any)
func main() {
printf(cstr("Hello, world\n"))
}

27
cl/_testcgo/cstr/out.ll Normal file
View File

@@ -0,0 +1,27 @@
; ModuleID = 'main'
source_filename = "main"
@"main.init$guard" = global ptr null
@0 = private unnamed_addr constant [14 x i8] c"Hello, world\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 void @main() {
_llgo_0:
call void @main.init()
call void (ptr, ...) @printf(ptr @0)
ret void
}
declare void @printf(ptr, ...)

View File

@@ -0,0 +1,14 @@
package main
import (
"github.com/goplus/llgo/internal/runtime/c"
)
func foo() {
c.Unreachable()
}
func main() {
foo()
c.Printf(c.String("Hello\n"))
}

View File

@@ -0,0 +1,38 @@
; ModuleID = 'main'
source_filename = "main"
%"github.com/goplus/llgo/internal/runtime.String" = type { ptr, i64 }
@"main.init$guard" = global ptr null
define void @main.foo() {
_llgo_0:
unreachable
ret void
}
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 void @main() {
_llgo_0:
call void @main.init()
call void @main.foo()
%0 = call ptr @"github.com/goplus/llgo/internal/runtime/c.String"([7 x i8] c"Hello\0A\00")
%1 = call i32 (ptr, ...) @printf(ptr %0)
ret void
}
declare ptr @"github.com/goplus/llgo/internal/runtime/c.String"(%"github.com/goplus/llgo/internal/runtime.String")
declare i32 @printf(ptr, ...)

View File

@@ -25,6 +25,34 @@ import (
"golang.org/x/tools/go/ssa"
)
func TestErrAlloca(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Fatal("alloca: no error?")
}
}()
var ctz context
ctz.alloca(nil, nil)
}
func TestCStrNoArgs(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Fatal("cstr: no error?")
}
}()
cstr(nil, nil)
}
func TestCStrNonconst(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Fatal("cstr: no error?")
}
}()
cstr(nil, []ssa.Value{&ssa.Parameter{}})
}
func TestPkgNoInit(t *testing.T) {
pkg := types.NewPackage("foo", "foo")
ctx := &context{

View File

@@ -189,8 +189,9 @@ func (p *context) compileGlobal(pkg llssa.Package, gbl *ssa.Global) {
func (p *context) compileFunc(pkg llssa.Package, pkgTypes *types.Package, f *ssa.Function) {
sig := f.Signature
name, ok := p.funcName(pkgTypes, f, true)
if !ok { // ignored
name, ftype := p.funcName(pkgTypes, f, true)
switch ftype {
case ignoredFunc, llgoInstr: // llgo extended instructions
return
}
if debugInstr {
@@ -269,6 +270,26 @@ func (p *context) checkVArgs(v *ssa.Alloc, t *types.Pointer) bool {
return false
}
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.CString(sv)
}
}
}
panic("cstr(<string-literal>): invalid arguments")
}
func (p *context) alloca(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) {
if len(args) == 1 {
n := p.compileValue(b, args[0])
return b.Alloca(n)
}
panic("alloca(size uintptr): invalid arguments")
}
func (p *context) compileInstrOrValue(b llssa.Builder, iv instrOrValue, asValue bool) (ret llssa.Expr) {
if asValue {
if v, ok := p.bvals[iv]; ok {
@@ -287,8 +308,9 @@ func (p *context) compileInstrOrValue(b llssa.Builder, iv instrOrValue, asValue
if debugGoSSA {
log.Println(">>> Call", cv, call.Args)
}
if builtin, ok := cv.(*ssa.Builtin); ok {
fn := builtin.Name()
switch cv := cv.(type) {
case *ssa.Builtin:
fn := cv.Name()
if fn == "ssa:wrapnilchk" { // TODO(xsw): check nil ptr
arg := call.Args[0]
ret = p.compileValue(b, arg)
@@ -297,10 +319,28 @@ func (p *context) compileInstrOrValue(b llssa.Builder, iv instrOrValue, asValue
args := p.compileValues(b, call.Args, kind)
ret = b.BuiltinCall(fn, args...)
}
} else {
fn := p.compileValue(b, cv)
args := p.compileValues(b, call.Args, kind)
ret = b.Call(fn, args...)
case *ssa.Function:
fn, ftype := p.funcOf(cv)
switch ftype {
case goFunc, cFunc:
args := p.compileValues(b, call.Args, kind)
ret = b.Call(fn.Expr, args...)
case llgoCstr:
ret = cstr(b, call.Args)
case llgoAlloca:
ret = p.alloca(b, call.Args)
case llgoUnreachable:
b.Unreachable()
default:
panic("todo")
}
default:
panic("todo")
/*
fn := p.compileValue(b, cv)
args := p.compileValues(b, call.Args, kind)
ret = b.Call(fn, args...)
*/
}
case *ssa.BinOp:
x := p.compileValue(b, v.X)
@@ -421,8 +461,14 @@ func (p *context) compileValue(b llssa.Builder, v ssa.Value) llssa.Expr {
}
}
case *ssa.Function:
fn := p.funcOf(v)
return fn.Expr
panic("unreachable")
/*
fn, ftype := p.funcOf(v)
if ftype >= llgoInstrBase {
panic("can't use llgo instruction as a value")
}
return fn.Expr
*/
case *ssa.Global:
g := p.varOf(v)
return g.Expr

View File

@@ -134,9 +134,9 @@ func (p *context) initLinkname(pkgPath, line string) {
text := strings.TrimSpace(line[len(linkname):])
if idx := strings.IndexByte(text, ' '); idx > 0 {
link := strings.TrimLeft(text[idx+1:], " ")
if strings.Contains(link, ".") { // eg. C.printf, C.strlen
if strings.Contains(link, ".") { // eg. C.printf, C.strlen, llgo.cstr
name := pkgPath + "." + text[:idx]
p.link[name] = link[2:]
p.link[name] = link
} else {
panic(line + ": no specified call convention. eg. //go:linkname Printf C.printf")
}
@@ -171,25 +171,56 @@ func checkCgo(fnName string) bool {
(fnName[4] == '_' || strings.HasPrefix(fnName[4:], "Check"))
}
func (p *context) funcName(pkg *types.Package, fn *ssa.Function, ignore bool) (string, bool) {
const (
ignoredFunc = iota
goFunc
cFunc
llgoInstr = -1
llgoInstrBase = 0x80
llgoCstr = llgoInstrBase + 1
llgoAlloca = llgoInstrBase + 2
llgoUnreachable = llgoInstrBase + 3
)
func (p *context) funcName(pkg *types.Package, fn *ssa.Function, ignore bool) (string, int) {
name := funcName(pkg, fn)
if ignore && ignoreName(name) || checkCgo(fn.Name()) {
return name, false
return name, ignoredFunc
}
if v, ok := p.link[name]; ok {
return v, true
if strings.HasPrefix(v, "C.") {
return v[2:], cFunc
}
if strings.HasPrefix(v, "llgo.") {
return v[5:], llgoInstr
}
return v, goFunc
}
return name, true
return name, goFunc
}
func (p *context) funcOf(fn *ssa.Function) llssa.Function {
// funcOf returns a function by name and set ftype = goFunc, cFunc, etc.
// or returns nil and set ftype = llgoCstr, llgoAlloca, llgoUnreachable, etc.
func (p *context) funcOf(fn *ssa.Function) (ret llssa.Function, ftype int) {
pkgTypes := p.ensureLoaded(fn.Pkg.Pkg)
pkg := p.pkg
name, _ := p.funcName(pkgTypes, fn, false)
if ret := pkg.FuncOf(name); ret != nil {
return ret
name, ftype := p.funcName(pkgTypes, fn, false)
if ftype == llgoInstr {
switch name {
case "cstr":
ftype = llgoCstr
case "alloca":
ftype = llgoAlloca
case "unreachable":
ftype = llgoUnreachable
default:
panic("unknown llgo instruction: " + name)
}
} else if ret = pkg.FuncOf(name); ret == nil {
ret = pkg.NewFunc(name, fn.Signature)
}
return pkg.NewFunc(name, fn.Signature)
return
}
func (p *context) varOf(v *ssa.Global) llssa.Global {

2
go.mod
View File

@@ -5,7 +5,7 @@ go 1.18
require (
github.com/aykevl/go-wasm v0.0.1
github.com/goplus/gogen v1.15.2
github.com/goplus/llvm v0.7.2
github.com/goplus/llvm v0.7.3-0.20240429101120-83f0ad1fd7b3
github.com/goplus/mod v0.13.10
github.com/qiniu/x v1.13.10
golang.org/x/tools v0.20.0

4
go.sum
View File

@@ -6,6 +6,10 @@ github.com/goplus/llvm v0.7.1 h1:B12Fr/wc3pAsq5PLuac9u9IuKpLRuCufdVAeGDP/MRw=
github.com/goplus/llvm v0.7.1/go.mod h1:PeVK8GgzxwAYCiMiUAJb5wJR6xbhj989tu9oulKLLT4=
github.com/goplus/llvm v0.7.2 h1:NL3LlwAmYVCGA6yV40AjOvMDKl2dbCqoYPtugmLQK+E=
github.com/goplus/llvm v0.7.2/go.mod h1:PeVK8GgzxwAYCiMiUAJb5wJR6xbhj989tu9oulKLLT4=
github.com/goplus/llvm v0.7.3-0.20240429063826-4d6268bd1670 h1:M5fGMLl9vj1W4lSKrOkhzVpoyyMOXtfAU5MsukbhUnY=
github.com/goplus/llvm v0.7.3-0.20240429063826-4d6268bd1670/go.mod h1:PeVK8GgzxwAYCiMiUAJb5wJR6xbhj989tu9oulKLLT4=
github.com/goplus/llvm v0.7.3-0.20240429101120-83f0ad1fd7b3 h1:FWne6LdbQ0p+lGBpkdmCYzcJDDTH85ygkkWKy2Pd3EQ=
github.com/goplus/llvm v0.7.3-0.20240429101120-83f0ad1fd7b3/go.mod h1:PeVK8GgzxwAYCiMiUAJb5wJR6xbhj989tu9oulKLLT4=
github.com/goplus/mod v0.13.10 h1:5Om6KOvo31daN7N30kWU1vC5zhsJPM+uPbcEN/FnlzE=
github.com/goplus/mod v0.13.10/go.mod h1:HDuPZgpWiaTp3PUolFgsiX+Q77cbUWB/mikVHfYND3c=
github.com/qiniu/x v1.13.10 h1:J4Z3XugYzAq85SlyAfqlKVrbf05glMbAOh+QncsDQpE=

View File

@@ -16,19 +16,20 @@
package c
import "C"
import "unsafe"
const (
LLGoPackage = "decl"
)
//go:linkname String llgo.CString
//go:linkname String llgo.cstr
func String(string) *int8
//go:linkname Alloca llgo.Alloca
//go:linkname Alloca llgo.alloca
func Alloca(size uintptr) unsafe.Pointer
//go:linkname Unreachable llgo.Unreachable
//go:linkname Unreachable llgo.unreachable
func Unreachable()
//go:linkname Malloc C.malloc
@@ -38,4 +39,4 @@ func Malloc(size uintptr) unsafe.Pointer
func Memcpy(dst, src unsafe.Pointer, n uintptr) unsafe.Pointer
//go:linkname Printf C.printf
func Printf(format *int8, __llgo_va_list ...any)
func Printf(format *int8, __llgo_va_list ...any) C.int

View File

@@ -82,12 +82,6 @@ func (p Program) Null(t Type) Expr {
return Expr{llvm.ConstNull(t.ll), t}
}
// CStringVal returns a c-style string constant expression.
func (p Program) CStringVal(v string) Expr {
t := p.CString()
return Expr{llvm.ConstString(v, true), t}
}
// StringVal returns string constant expression.
func (p Program) StringVal(v string) Expr {
t := p.String()
@@ -132,7 +126,7 @@ func (p Program) Val(v interface{}) Expr {
// Const returns a constant expression.
func (b Builder) Const(v constant.Value, typ Type) Expr {
prog := b.prog
prog := b.Prog
if v == nil {
return prog.Null(typ)
}
@@ -153,6 +147,11 @@ func (b Builder) Const(v constant.Value, typ Type) Expr {
panic("todo")
}
// CString returns a c-style string constant expression.
func (b Builder) CString(v string) Expr {
return Expr{llvm.CreateGlobalStringPtr(b.impl, v), b.Prog.CString()}
}
// -----------------------------------------------------------------------------
const (
@@ -277,7 +276,7 @@ func (b Builder) BinOp(op token.Token, x, y Expr) Expr {
}
return Expr{llvm.CreateBinOp(b.impl, llop, x.impl, y.impl), x.Type}
case isPredOp(op): // op: == != < <= < >=
tret := b.prog.Bool()
tret := b.Prog.Bool()
kind := x.kind
switch kind {
case vkSigned:
@@ -318,7 +317,7 @@ func (b Builder) Load(ptr Expr) Expr {
if debugInstr {
log.Printf("Load %v\n", ptr.impl)
}
telem := b.prog.Elem(ptr.Type)
telem := b.Prog.Elem(ptr.Type)
return Expr{llvm.CreateLoad(b.impl, telem.ll, ptr.impl), telem}
}
@@ -354,7 +353,7 @@ func (b Builder) FieldAddr(x Expr, idx int) Expr {
if debugInstr {
log.Printf("FieldAddr %v, %d\n", x.impl, idx)
}
prog := b.prog
prog := b.Prog
tstruc := prog.Elem(x.Type)
telem := prog.Field(tstruc, idx)
pt := prog.Pointer(telem)
@@ -377,7 +376,7 @@ func (b Builder) IndexAddr(x, idx Expr) Expr {
if debugInstr {
log.Printf("IndexAddr %v, %v\n", x.impl, idx.impl)
}
prog := b.prog
prog := b.Prog
telem := prog.Index(x.Type)
pt := prog.Pointer(telem)
indices := []llvm.Value{idx.impl}
@@ -407,7 +406,7 @@ func (b Builder) Alloc(t *types.Pointer, heap bool) (ret Expr) {
if debugInstr {
log.Printf("Alloc %v, %v\n", t, heap)
}
prog := b.prog
prog := b.Prog
telem := t.Elem()
if heap {
pkg := b.fn.pkg
@@ -421,6 +420,30 @@ func (b Builder) Alloc(t *types.Pointer, heap bool) (ret Expr) {
return
}
// Alloca allocates 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 = &aType{prog.tyVoidPtr(), types.Typ[types.UnsafePointer], vkPtr}
return
}
/*
// 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
}
*/
// The ChangeType instruction applies to X a value-preserving type
// change to Type().
//
@@ -452,7 +475,7 @@ func (b Builder) ChangeType(t Type, x Expr) (ret Expr) {
switch typ.(type) {
default:
ret.impl = b.impl.CreateBitCast(x.impl, t.ll, "bitCast")
ret.Type = b.prog.Type(typ)
ret.Type = b.Prog.Type(typ)
return
}
}
@@ -488,7 +511,7 @@ func (b Builder) ChangeType(t Type, x Expr) (ret Expr) {
// t1 = convert []byte <- string (t0)
func (b Builder) Convert(t Type, x Expr) (ret Expr) {
typ := t.t
ret.Type = b.prog.Type(typ)
ret.Type = b.Prog.Type(typ)
switch und := typ.Underlying().(type) {
case *types.Basic:
kind := und.Kind()
@@ -624,7 +647,7 @@ func (b Builder) TypeAssert(x Expr, assertedTyp Type, commaOk bool) (ret Expr) {
default:
panic("todo")
}
typ := b.InlineCall(pkg.rtFunc("Basic"), b.prog.Val(int(kind)))
typ := b.InlineCall(pkg.rtFunc("Basic"), b.Prog.Val(int(kind)))
return b.InlineCall(fn, x, typ)
}
panic("todo")
@@ -659,7 +682,7 @@ func (b Builder) Call(fn Expr, args ...Expr) (ret Expr) {
}
switch t := fn.t.(type) {
case *types.Signature:
ret.Type = b.prog.retType(t)
ret.Type = b.Prog.retType(t)
default:
panic("todo")
}

View File

@@ -50,7 +50,7 @@ func (p BasicBlock) Index() int {
type aBuilder struct {
impl llvm.Builder
fn Function
prog Program
Prog Program
}
// Builder represents a builder for creating instructions in a function.
@@ -76,6 +76,11 @@ func (b Builder) Panic(v Expr) {
b.impl.CreateUnreachable() // TODO(xsw): pass v
}
// Unreachable emits an unreachable instruction.
func (b Builder) Unreachable() {
b.impl.CreateUnreachable()
}
// Return emits a return instruction.
func (b Builder) Return(results ...Expr) {
if debugInstr {