diff --git a/cl/_testrt/asmfull/in.go b/cl/_testrt/asmfull/in.go new file mode 100644 index 00000000..e5068fa2 --- /dev/null +++ b/cl/_testrt/asmfull/in.go @@ -0,0 +1,17 @@ +package main + +import _ "unsafe" + +//go:linkname asm llgo.asm +func asm(instruction string) + +//go:linkname asmFull llgo.asm +func asmFull(instruction string, regs map[string]any) uintptr + +func main() { + asm("nop") + result := asmFull("mov {}, {value}", map[string]any{ + "value": uint32(42), + }) + _ = result +} diff --git a/cl/_testrt/asmfull/out.ll b/cl/_testrt/asmfull/out.ll new file mode 100644 index 00000000..68c62ac6 --- /dev/null +++ b/cl/_testrt/asmfull/out.ll @@ -0,0 +1,155 @@ +; ModuleID = 'github.com/goplus/llgo/cl/_testrt/asmfull' +source_filename = "github.com/goplus/llgo/cl/_testrt/asmfull" + +%"github.com/goplus/llgo/runtime/internal/runtime.eface" = type { ptr, ptr } +%"github.com/goplus/llgo/runtime/internal/runtime.String" = type { ptr, i64 } +%"github.com/goplus/llgo/runtime/internal/runtime.Slice" = type { ptr, i64, i64 } +%"github.com/goplus/llgo/runtime/abi.StructField" = type { %"github.com/goplus/llgo/runtime/internal/runtime.String", ptr, i64, %"github.com/goplus/llgo/runtime/internal/runtime.String", i1 } + +@"github.com/goplus/llgo/cl/_testrt/asmfull.init$guard" = global i1 false, align 1 +@_llgo_string = linkonce global ptr null, align 8 +@_llgo_any = linkonce global ptr null, align 8 +@0 = private unnamed_addr constant [41 x i8] c"github.com/goplus/llgo/cl/_testrt/asmfull", align 1 +@"map[_llgo_string]_llgo_any" = linkonce global ptr null, align 8 +@1 = private unnamed_addr constant [7 x i8] c"topbits", align 1 +@2 = private unnamed_addr constant [4 x i8] c"keys", align 1 +@3 = private unnamed_addr constant [5 x i8] c"elems", align 1 +@4 = private unnamed_addr constant [8 x i8] c"overflow", align 1 +@_llgo_uint32 = linkonce global ptr null, align 8 +@5 = private unnamed_addr constant [5 x i8] c"value", align 1 + +define void @"github.com/goplus/llgo/cl/_testrt/asmfull.init"() { +_llgo_0: + %0 = load i1, ptr @"github.com/goplus/llgo/cl/_testrt/asmfull.init$guard", align 1 + br i1 %0, label %_llgo_2, label %_llgo_1 + +_llgo_1: ; preds = %_llgo_0 + store i1 true, ptr @"github.com/goplus/llgo/cl/_testrt/asmfull.init$guard", align 1 + call void @"github.com/goplus/llgo/cl/_testrt/asmfull.init$after"() + br label %_llgo_2 + +_llgo_2: ; preds = %_llgo_1, %_llgo_0 + ret void +} + +define void @"github.com/goplus/llgo/cl/_testrt/asmfull.main"() { +_llgo_0: + call void asm sideeffect "nop", ""() + %0 = load ptr, ptr @_llgo_string, align 8 + %1 = load ptr, ptr @_llgo_any, align 8 + %2 = load ptr, ptr @"map[_llgo_string]_llgo_any", align 8 + %3 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.MakeMap"(ptr %2, i64 1) + %4 = load ptr, ptr @_llgo_uint32, align 8 + %5 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.eface" undef, ptr %4, 0 + %6 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.eface" %5, ptr inttoptr (i64 42 to ptr), 1 + %7 = load ptr, ptr @"map[_llgo_string]_llgo_any", align 8 + %8 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 16) + store %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @5, i64 5 }, ptr %8, align 8 + %9 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.MapAssign"(ptr %7, ptr %3, ptr %8) + store %"github.com/goplus/llgo/runtime/internal/runtime.eface" %6, ptr %9, align 8 + ret void +} + +define void @"github.com/goplus/llgo/cl/_testrt/asmfull.init$after"() { +_llgo_0: + %0 = load ptr, ptr @_llgo_string, align 8 + %1 = icmp eq ptr %0, null + br i1 %1, label %_llgo_1, label %_llgo_2 + +_llgo_1: ; preds = %_llgo_0 + %2 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Basic"(i64 24) + store ptr %2, ptr @_llgo_string, align 8 + br label %_llgo_2 + +_llgo_2: ; preds = %_llgo_1, %_llgo_0 + %3 = load ptr, ptr @_llgo_any, align 8 + %4 = icmp eq ptr %3, null + br i1 %4, label %_llgo_3, label %_llgo_4 + +_llgo_3: ; preds = %_llgo_2 + %5 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 0) + %6 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" undef, ptr %5, 0 + %7 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %6, i64 0, 1 + %8 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %7, i64 0, 2 + %9 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Interface"(%"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @0, i64 41 }, %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %8) + store ptr %9, ptr @_llgo_any, align 8 + br label %_llgo_4 + +_llgo_4: ; preds = %_llgo_3, %_llgo_2 + %10 = load ptr, ptr @"map[_llgo_string]_llgo_any", align 8 + %11 = icmp eq ptr %10, null + br i1 %11, label %_llgo_5, label %_llgo_6 + +_llgo_5: ; preds = %_llgo_4 + %12 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Basic"(i64 24) + %13 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 0) + %14 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" undef, ptr %13, 0 + %15 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %14, i64 0, 1 + %16 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %15, i64 0, 2 + %17 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Interface"(%"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @0, i64 41 }, %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %16) + %18 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Basic"(i64 40) + %19 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.ArrayOf"(i64 8, ptr %18) + %20 = call %"github.com/goplus/llgo/runtime/abi.StructField" @"github.com/goplus/llgo/runtime/internal/runtime.StructField"(%"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @1, i64 7 }, ptr %19, i64 0, %"github.com/goplus/llgo/runtime/internal/runtime.String" zeroinitializer, i1 false) + %21 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Basic"(i64 24) + %22 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.ArrayOf"(i64 8, ptr %21) + %23 = call %"github.com/goplus/llgo/runtime/abi.StructField" @"github.com/goplus/llgo/runtime/internal/runtime.StructField"(%"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @2, i64 4 }, ptr %22, i64 8, %"github.com/goplus/llgo/runtime/internal/runtime.String" zeroinitializer, i1 false) + %24 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 0) + %25 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" undef, ptr %24, 0 + %26 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %25, i64 0, 1 + %27 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %26, i64 0, 2 + %28 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Interface"(%"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @0, i64 41 }, %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %27) + %29 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.ArrayOf"(i64 8, ptr %28) + %30 = call %"github.com/goplus/llgo/runtime/abi.StructField" @"github.com/goplus/llgo/runtime/internal/runtime.StructField"(%"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @3, i64 5 }, ptr %29, i64 136, %"github.com/goplus/llgo/runtime/internal/runtime.String" zeroinitializer, i1 false) + %31 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Basic"(i64 58) + %32 = call %"github.com/goplus/llgo/runtime/abi.StructField" @"github.com/goplus/llgo/runtime/internal/runtime.StructField"(%"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @4, i64 8 }, ptr %31, i64 264, %"github.com/goplus/llgo/runtime/internal/runtime.String" zeroinitializer, i1 false) + %33 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 224) + %34 = getelementptr %"github.com/goplus/llgo/runtime/abi.StructField", ptr %33, i64 0 + store %"github.com/goplus/llgo/runtime/abi.StructField" %20, ptr %34, align 8 + %35 = getelementptr %"github.com/goplus/llgo/runtime/abi.StructField", ptr %33, i64 1 + store %"github.com/goplus/llgo/runtime/abi.StructField" %23, ptr %35, align 8 + %36 = getelementptr %"github.com/goplus/llgo/runtime/abi.StructField", ptr %33, i64 2 + store %"github.com/goplus/llgo/runtime/abi.StructField" %30, ptr %36, align 8 + %37 = getelementptr %"github.com/goplus/llgo/runtime/abi.StructField", ptr %33, i64 3 + store %"github.com/goplus/llgo/runtime/abi.StructField" %32, ptr %37, align 8 + %38 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" undef, ptr %33, 0 + %39 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %38, i64 4, 1 + %40 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %39, i64 4, 2 + %41 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Struct"(%"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @0, i64 41 }, i64 272, %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %40) + %42 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.MapOf"(ptr %12, ptr %17, ptr %41, i64 12) + call void @"github.com/goplus/llgo/runtime/internal/runtime.SetDirectIface"(ptr %42) + store ptr %42, ptr @"map[_llgo_string]_llgo_any", align 8 + br label %_llgo_6 + +_llgo_6: ; preds = %_llgo_5, %_llgo_4 + %43 = load ptr, ptr @_llgo_uint32, align 8 + %44 = icmp eq ptr %43, null + br i1 %44, label %_llgo_7, label %_llgo_8 + +_llgo_7: ; preds = %_llgo_6 + %45 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Basic"(i64 42) + store ptr %45, ptr @_llgo_uint32, align 8 + br label %_llgo_8 + +_llgo_8: ; preds = %_llgo_7, %_llgo_6 + ret void +} + +declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.Basic"(i64) + +declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.Interface"(%"github.com/goplus/llgo/runtime/internal/runtime.String", %"github.com/goplus/llgo/runtime/internal/runtime.Slice") + +declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64) + +declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.MapOf"(ptr, ptr, ptr, i64) + +declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.Struct"(%"github.com/goplus/llgo/runtime/internal/runtime.String", i64, %"github.com/goplus/llgo/runtime/internal/runtime.Slice") + +declare %"github.com/goplus/llgo/runtime/abi.StructField" @"github.com/goplus/llgo/runtime/internal/runtime.StructField"(%"github.com/goplus/llgo/runtime/internal/runtime.String", ptr, i64, %"github.com/goplus/llgo/runtime/internal/runtime.String", i1) + +declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.ArrayOf"(i64, ptr) + +declare void @"github.com/goplus/llgo/runtime/internal/runtime.SetDirectIface"(ptr) + +declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.MakeMap"(ptr, i64) + +declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.MapAssign"(ptr, ptr, ptr) diff --git a/cl/instr.go b/cl/instr.go index 81c4d325..8bea9c25 100644 --- a/cl/instr.go +++ b/cl/instr.go @@ -17,9 +17,12 @@ package cl import ( + "fmt" "go/constant" "go/types" "log" + "regexp" + "strings" "golang.org/x/tools/go/ssa" @@ -67,20 +70,87 @@ func cstr(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { } // func asm(string) -// func asmFull(string, map[string]any) uintptr -func asm(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { +// func asmFull(string, map[string]any) +func (p *context) asm(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { if len(args) == 1 { if sv, ok := constStr(args[0]); ok { b.InlineAsm(sv) return llssa.Expr{Type: b.Prog.Void()} } } else if len(args) == 2 { - // todo(zzy): Implement asmFull logic here - panic("asmFull: not implemented yet") + return p.asmFull(b, args) } panic("asm: invalid arguments - expected asm() or asm(, )") } +// asmFull is a compiler builtin which emits inline assembly. +func (p *context) asmFull(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { + asmString, ok := constStr(args[0]) + if !ok { + panic("asmFull: inline assembly requires a constant string") + } + + registers := make(map[string]llssa.Expr) + if registerMap, ok := args[1].(*ssa.MakeMap); ok { + referrers := registerMap.Referrers() + for _, r := range *referrers { + switch r := r.(type) { + case *ssa.MapUpdate: + if r.Block() != registerMap.Block() { + panic("asmFull: register value map must be created in the same basic block") + } + + key, ok := constStr(r.Key) + if !ok { + panic("asmFull: register key must be a string constant") + } + + llvmValue := p.compileValue(b, r.Value.(*ssa.MakeInterface).X) + registers[key] = llvmValue + } + } + } + + finalAsm := asmString + var hasOutput bool + var inputValues []llssa.Expr + var constraints []string + registerNumbers := map[string]int{} + // todo(zzy):output type + _ = hasOutput + + if strings.Contains(finalAsm, "{}") { + finalAsm = strings.ReplaceAll(finalAsm, "{}", "$0") + constraints = append(constraints, "=&r") + registerNumbers[""] = 0 + hasOutput = true + } + + finalAsm = regexp.MustCompile(`\{[a-zA-Z]+\}`).ReplaceAllStringFunc(finalAsm, func(s string) string { + // TODO: skip strings like {r4} etc. that look like ARM push/pop + // instructions. + name := s[1 : len(s)-1] + value, ok := registers[name] + if !ok { + panic("asmFull: register not found: " + name) + } + if _, ok := registerNumbers[name]; !ok { + registerNumbers[name] = len(registerNumbers) + inputValues = append(inputValues, value) + constraints = append(constraints, "r") + } + return fmt.Sprintf("${%v}", registerNumbers[name]) + }) + + constraintStr := strings.Join(constraints, ",") + if debugInstr { + log.Printf("asmFull: %q -> %q, constraints: %q", asmString, finalAsm, constraintStr) + } + + // todo(zzy): call b.InlineAsmFull + return b.Const(constant.MakeInt64(0x1234), b.Prog.Uintptr()) +} + // ----------------------------------------------------------------------------- // func _Cfunc_CString(s string) *int8 @@ -474,7 +544,7 @@ func (p *context) call(b llssa.Builder, act llssa.DoAction, call *ssa.CallCommon case llgoCstr: ret = cstr(b, args) case llgoAsm: - ret = asm(b, args) + ret = p.asm(b, args) case llgoCgoCString: ret = p.cgoCString(b, args) case llgoCgoCBytes: