cl: support llgo.cstr

This commit is contained in:
xushiwei
2024-04-29 13:59:06 +08:00
parent 637db665c3
commit 4eb2ddaf15
8 changed files with 147 additions and 31 deletions

13
cl/_testdata/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"))
}

26
cl/_testdata/cstr/out.ll Normal file
View File

@@ -0,0 +1,26 @@
; ModuleID = 'main'
source_filename = "main"
@"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()
call void (ptr, ...) @printf([14 x i8] c"Hello, world\0A\00")
ret void
}
declare void @printf(ptr, ...)

View File

@@ -25,6 +25,24 @@ import (
"golang.org/x/tools/go/ssa"
)
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,18 @@ 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.Prog.CStringVal(sv)
}
}
}
panic("cstr(<string-literal>): 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 +300,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,7 +311,18 @@ func (p *context) compileInstrOrValue(b llssa.Builder, iv instrOrValue, asValue
args := p.compileValues(b, call.Args, kind)
ret = b.BuiltinCall(fn, args...)
}
} else {
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)
default:
panic("todo")
}
default:
fn := p.compileValue(b, cv)
args := p.compileValues(b, call.Args, kind)
ret = b.Call(fn, args...)
@@ -421,7 +446,10 @@ func (p *context) compileValue(b llssa.Builder, v ssa.Value) llssa.Expr {
}
}
case *ssa.Function:
fn := p.funcOf(v)
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)

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 {

View File

@@ -22,13 +22,13 @@ 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

View File

@@ -132,7 +132,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)
}
@@ -277,7 +277,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 +318,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 +354,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 +377,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 +407,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
@@ -452,7 +452,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 +488,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 +624,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 +659,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.