diff --git a/_demo/asmfullcall/asmfullcall.go b/_demo/asmfullcall/asmfullcall.go new file mode 100644 index 00000000..5ced4d85 --- /dev/null +++ b/_demo/asmfullcall/asmfullcall.go @@ -0,0 +1,21 @@ +package main + +import ( + "fmt" +) + +//llgo:link asmFull llgo.asm +func asmFull(instruction string, regs map[string]any) uintptr { return 0 } + +var testVar = 0 + +func main() { + verify() +} + +func check(expected, actual int) { + if expected != actual { + panic(fmt.Sprintf("Expected: %d, Got: %d\n", expected, actual)) + } + fmt.Println("asm check passed:", actual) +} diff --git a/_demo/asmfullcall/asmfullcall_darwin.go b/_demo/asmfullcall/asmfullcall_darwin.go new file mode 100644 index 00000000..3f1fbcbd --- /dev/null +++ b/_demo/asmfullcall/asmfullcall_darwin.go @@ -0,0 +1,31 @@ +//go:build darwin && arm64 + +package main + +import "unsafe" + +func verify() { + // 0 output & 0 input + asmFull("nop", nil) + + // 0 output & 1 input with memory address + addr := uintptr(unsafe.Pointer(&testVar)) + asmFull("str {value}, [{addr}]", map[string]any{ + "addr": addr, + "value": 43, + }) + check(43, testVar) + + // 1 output & 1 input + res1 := asmFull("mov {}, {value}", map[string]any{ + "value": 41, + }) + check(41, int(res1)) + + // 1 output & 2 inputs + res2 := asmFull("add {}, {a}, {b}", map[string]any{ + "a": 25, + "b": 17, + }) + check(42, int(res2)) +} diff --git a/_demo/asmfullcall/asmfullcall_linux.go b/_demo/asmfullcall/asmfullcall_linux.go new file mode 100644 index 00000000..6a3f4a79 --- /dev/null +++ b/_demo/asmfullcall/asmfullcall_linux.go @@ -0,0 +1,30 @@ +//go:build linux && amd64 + +package main + +import "unsafe" + +func verify() { + // 0 output & 0 input + asmFull("nop", nil) + + // 0 output & 1 input with memory address + addr := uintptr(unsafe.Pointer(&testVar)) + asmFull("movq {value}, ({addr})", map[string]any{ + "addr": addr, + "value": 43, + }) + check(43, testVar) + + // 1 output & 1 input + res1 := asmFull("movq {value}, {}", map[string]any{ + "value": 41, + }) + check(41, int(res1)) + + res2 := asmFull("leaq ({a},{b}), {}", map[string]any{ + "a": 25, + "b": 17, + }) + check(42, int(res2)) +} diff --git a/cl/_testrt/asmfull/in.go b/cl/_testrt/asmfull/in.go new file mode 100644 index 00000000..4bfa5933 --- /dev/null +++ b/cl/_testrt/asmfull/in.go @@ -0,0 +1,27 @@ +package main + +import _ "unsafe" + +//go:linkname asmFull llgo.asm +func asmFull(instruction string, regs map[string]any) uintptr + +func main() { + // no input,no return value + asmFull("nop", nil) + // input only,no return value + asmFull("# test value {value}", map[string]any{"value": 42}) + // input with return value + res1 := asmFull("mov {}, {value}", map[string]any{ + "value": 42, + }) + println("Result:", res1) + // note(zzy): multiple inputs with return value + // only for test register & constraint,not have actual meaning + // the ir compare cannot crossplatform currently + // so just use a comment to test it + res2 := asmFull("# calc {x} + {y} -> {}", map[string]any{ + "x": 25, + "y": 17, + }) + println("Result:", res2) +} diff --git a/cl/_testrt/asmfull/out.ll b/cl/_testrt/asmfull/out.ll new file mode 100644 index 00000000..e7fcf275 --- /dev/null +++ b/cl/_testrt/asmfull/out.ll @@ -0,0 +1,203 @@ +; 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_int = linkonce global ptr null, align 8 +@5 = private unnamed_addr constant [5 x i8] c"value", align 1 +@6 = private unnamed_addr constant [7 x i8] c"Result:", align 1 +@7 = private unnamed_addr constant [1 x i8] c"x", align 1 +@8 = private unnamed_addr constant [1 x i8] c"y", 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_int, 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 + call void asm sideeffect "# test value ${0}", "r"(i64 42) + %10 = load ptr, ptr @"map[_llgo_string]_llgo_any", align 8 + %11 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.MakeMap"(ptr %10, i64 1) + %12 = load ptr, ptr @_llgo_int, align 8 + %13 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.eface" undef, ptr %12, 0 + %14 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.eface" %13, ptr inttoptr (i64 42 to ptr), 1 + %15 = load ptr, ptr @"map[_llgo_string]_llgo_any", align 8 + %16 = 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 %16, align 8 + %17 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.MapAssign"(ptr %15, ptr %11, ptr %16) + store %"github.com/goplus/llgo/runtime/internal/runtime.eface" %14, ptr %17, align 8 + %18 = call i64 asm sideeffect "mov $0, ${1}", "=&r,r"(i64 42) + call void @"github.com/goplus/llgo/runtime/internal/runtime.PrintString"(%"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @6, i64 7 }) + call void @"github.com/goplus/llgo/runtime/internal/runtime.PrintByte"(i8 32) + call void @"github.com/goplus/llgo/runtime/internal/runtime.PrintUint"(i64 %18) + call void @"github.com/goplus/llgo/runtime/internal/runtime.PrintByte"(i8 10) + %19 = load ptr, ptr @"map[_llgo_string]_llgo_any", align 8 + %20 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.MakeMap"(ptr %19, i64 2) + %21 = load ptr, ptr @_llgo_int, align 8 + %22 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.eface" undef, ptr %21, 0 + %23 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.eface" %22, ptr inttoptr (i64 25 to ptr), 1 + %24 = load ptr, ptr @"map[_llgo_string]_llgo_any", align 8 + %25 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 16) + store %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @7, i64 1 }, ptr %25, align 8 + %26 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.MapAssign"(ptr %24, ptr %20, ptr %25) + store %"github.com/goplus/llgo/runtime/internal/runtime.eface" %23, ptr %26, align 8 + %27 = load ptr, ptr @_llgo_int, align 8 + %28 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.eface" undef, ptr %27, 0 + %29 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.eface" %28, ptr inttoptr (i64 17 to ptr), 1 + %30 = load ptr, ptr @"map[_llgo_string]_llgo_any", align 8 + %31 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 16) + store %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @8, i64 1 }, ptr %31, align 8 + %32 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.MapAssign"(ptr %30, ptr %20, ptr %31) + store %"github.com/goplus/llgo/runtime/internal/runtime.eface" %29, ptr %32, align 8 + %33 = call i64 asm sideeffect "# calc ${1} + ${2} -> $0", "=&r,r,r"(i64 25, i64 17) + call void @"github.com/goplus/llgo/runtime/internal/runtime.PrintString"(%"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @6, i64 7 }) + call void @"github.com/goplus/llgo/runtime/internal/runtime.PrintByte"(i8 32) + call void @"github.com/goplus/llgo/runtime/internal/runtime.PrintUint"(i64 %33) + call void @"github.com/goplus/llgo/runtime/internal/runtime.PrintByte"(i8 10) + 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_int, 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 34) + store ptr %45, ptr @_llgo_int, 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) + +declare void @"github.com/goplus/llgo/runtime/internal/runtime.PrintString"(%"github.com/goplus/llgo/runtime/internal/runtime.String") + +declare void @"github.com/goplus/llgo/runtime/internal/runtime.PrintByte"(i8) + +declare void @"github.com/goplus/llgo/runtime/internal/runtime.PrintUint"(i64) diff --git a/cl/builtin_test.go b/cl/builtin_test.go index dfadaabb..a07e5f39 100644 --- a/cl/builtin_test.go +++ b/cl/builtin_test.go @@ -270,6 +270,52 @@ func TestErrBuiltin(t *testing.T) { test("atomicCmpXchg", func(ctx *context) { ctx.atomicCmpXchg(nil, nil) }) } +func TestErrAsm(t *testing.T) { + test := func(testName string, fn func(ctx *context)) { + defer func() { + if r := recover(); r == nil { + t.Fatal(testName, ": no error?") + } + }() + var ctx context + fn(&ctx) + } + + test("asm(NoArgs)", func(ctx *context) { ctx.asm(nil, []ssa.Value{}) }) + test("asm(Nonconst)", func(ctx *context) { ctx.asm(nil, []ssa.Value{&ssa.Parameter{}}) }) + test("asmFull(Nonconst)", func(ctx *context) { ctx.asm(nil, []ssa.Value{&ssa.Parameter{}, &ssa.Parameter{}}) }) + test("asmFull(NonConstKey)", func(ctx *context) { + makeMap := &ssa.MakeMap{} + nonConstKey := &ssa.Parameter{} + mapUpdate := &ssa.MapUpdate{Key: nonConstKey} + referrers := []ssa.Instruction{mapUpdate} + setRefs(unsafe.Pointer(makeMap), referrers...) + strConst := &ssa.Const{ + Value: constant.MakeString("nop"), + } + ctx.asm(nil, []ssa.Value{strConst, makeMap}) + }) + test("asmFull(RegisterNotFound)", func(ctx *context) { + makeMap := &ssa.MakeMap{} + referrers := []ssa.Instruction{} + setRefs(unsafe.Pointer(makeMap), referrers...) + strConst := &ssa.Const{ + Value: constant.MakeString("test {missing}"), + } + ctx.asm(nil, []ssa.Value{strConst, makeMap}) + }) + test("asmFull(UnknownReferrer)", func(ctx *context) { + makeMap := &ssa.MakeMap{} + unknownRef := &ssa.Return{} + referrers := []ssa.Instruction{unknownRef} + setRefs(unsafe.Pointer(makeMap), referrers...) + strConst := &ssa.Const{ + Value: constant.MakeString("test"), + } + ctx.asm(nil, []ssa.Value{strConst, makeMap}) + }) +} + func TestPkgNoInit(t *testing.T) { pkg := types.NewPackage("foo", "foo") ctx := &context{ diff --git a/cl/instr.go b/cl/instr.go index e988bc01..5dabd5f8 100644 --- a/cl/instr.go +++ b/cl/instr.go @@ -17,15 +17,20 @@ package cl import ( + "fmt" "go/constant" "go/types" "log" + "regexp" + "strings" "golang.org/x/tools/go/ssa" llssa "github.com/goplus/llgo/ssa" ) +var asmRegisterRegex = regexp.MustCompile(`\{[a-zA-Z]+\}`) + // ----------------------------------------------------------------------------- func constStr(v ssa.Value) (ret string, ok bool) { @@ -67,14 +72,93 @@ func cstr(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { } // func asm(string) -func asm(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { +// func asm(string, map[string]any) +func (p *context) asm(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { + if len(args) == 0 || len(args) > 2 { + panic("asm: invalid arguments - expected asm() or asm(, )") + } + + asmString, ok := constStr(args[0]) + if !ok { + panic("asm: inline assembly requires a constant string") + } if len(args) == 1 { - if sv, ok := constStr(args[0]); ok { - b.InlineAsm(sv) - return llssa.Expr{Type: b.Prog.Void()} + b.InlineAsm(asmString) + return llssa.Expr{Type: b.Prog.Void()} + } + + 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.DebugRef, *ssa.Call: + // ignore + case *ssa.MapUpdate: + if r.Block() != registerMap.Block() { + panic("asm: register value map must be created in the same basic block") + } + key, ok := constStr(r.Key) + if !ok { + panic("asm: register key must be a string constant") + } + llvmValue := p.compileValue(b, r.Value.(*ssa.MakeInterface).X) + registers[key] = llvmValue + default: + panic(fmt.Sprintf("asm: don't know how to handle argument to inline assembly: %s", r.String())) + } } } - panic("asm(): invalid arguments") + + finalAsm := asmString + var hasOutput bool + var inputValues []llssa.Expr + var constraints []string + registerNumbers := map[string]int{} + + if strings.Contains(finalAsm, "{}") { + finalAsm = strings.ReplaceAll(finalAsm, "{}", "$0") + constraints = append(constraints, "=&r") + registerNumbers[""] = 0 + hasOutput = true + } + + finalAsm = asmRegisterRegex.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(fmt.Sprintf("asm: register not found: %s", name)) + } + if _, ok := registerNumbers[name]; !ok { + // Type checking - only allow integer basic types + rawType := value.Type.RawType() + if basic, ok := rawType.Underlying().(*types.Basic); ok && basic.Info()&types.IsInteger != 0 { + registerNumbers[name] = len(registerNumbers) + inputValues = append(inputValues, value) + constraints = append(constraints, "r") + } else { + // Pointer operands support was dropped, following TinyGo + // NOTE(tinygo): Memory references require a type starting with LLVM 14, probably as a preparation for opaque pointers. + panic(fmt.Sprintf("asm: unsupported type in inline assembly for operand: %s, only integer types are supported", name)) + } + } + return fmt.Sprintf("${%v}", registerNumbers[name]) + }) + + constraintStr := strings.Join(constraints, ",") + if debugInstr { + log.Printf("asm: %q -> %q, constraints: %q", asmString, finalAsm, constraintStr) + } + + if !hasOutput { + // Make sure we return something valid + b.InlineAsmFull(finalAsm, constraintStr, b.Prog.Void(), inputValues) + return b.Prog.Val((uintptr(0))) + } + + return b.InlineAsmFull(finalAsm, constraintStr, b.Prog.Uintptr(), inputValues) } // ----------------------------------------------------------------------------- @@ -470,7 +554,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: diff --git a/ssa/expr.go b/ssa/expr.go index 6f98cb38..b9dafca0 100644 --- a/ssa/expr.go +++ b/ssa/expr.go @@ -286,6 +286,18 @@ func (b Builder) InlineAsm(instruction string) { b.impl.CreateCall(typ, asm, nil, "") } +func (b Builder) InlineAsmFull(instruction, constraints string, retType Type, exprs []Expr) Expr { + typs := make([]llvm.Type, len(exprs)) + vals := make([]llvm.Value, len(exprs)) + for i, expr := range exprs { + typs[i], vals[i] = expr.Type.ll, expr.impl + } + + ftype := llvm.FunctionType(retType.ll, typs, false) + asm := llvm.InlineAsm(ftype, instruction, constraints, true, false, llvm.InlineAsmDialectATT, false) + return Expr{b.impl.CreateCall(ftype, asm, vals, ""), retType} +} + // GoString returns a Go string func (b Builder) GoString(v Expr) Expr { fn := b.Pkg.rtFunc("GoString")