From 485dbe402188e4222b13270fba980b38daf83d1b Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Wed, 20 Aug 2025 19:40:08 +0800 Subject: [PATCH 01/16] cl/instr:asm with two situation --- cl/instr.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cl/instr.go b/cl/instr.go index e988bc01..81c4d325 100644 --- a/cl/instr.go +++ b/cl/instr.go @@ -67,14 +67,18 @@ 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) { 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") } - panic("asm(): invalid arguments") + panic("asm: invalid arguments - expected asm() or asm(, )") } // ----------------------------------------------------------------------------- From ae36ef4a0e668a50fd9418319186a85ff31a2a9c Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Wed, 20 Aug 2025 19:40:22 +0800 Subject: [PATCH 02/16] cl/instr:replace register & build constraints --- cl/_testrt/asmfull/in.go | 17 +++++ cl/_testrt/asmfull/out.ll | 155 ++++++++++++++++++++++++++++++++++++++ cl/instr.go | 80 ++++++++++++++++++-- 3 files changed, 247 insertions(+), 5 deletions(-) create mode 100644 cl/_testrt/asmfull/in.go create mode 100644 cl/_testrt/asmfull/out.ll 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: From f5d4f93ed73a0e2c1e6e0d9727945b51d8476927 Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Wed, 20 Aug 2025 20:49:55 +0800 Subject: [PATCH 03/16] ssa:inlineAsmFull --- cl/_testrt/asmfull/in.go | 8 ++------ cl/_testrt/asmfull/out.ll | 23 +++++++++++++++++------ cl/instr.go | 5 +---- ssa/expr.go | 22 ++++++++++++++++++++++ 4 files changed, 42 insertions(+), 16 deletions(-) diff --git a/cl/_testrt/asmfull/in.go b/cl/_testrt/asmfull/in.go index e5068fa2..b17d87aa 100644 --- a/cl/_testrt/asmfull/in.go +++ b/cl/_testrt/asmfull/in.go @@ -2,16 +2,12 @@ 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), + "value": 42, }) - _ = result + println("Result:", result) } diff --git a/cl/_testrt/asmfull/out.ll b/cl/_testrt/asmfull/out.ll index 68c62ac6..1839d8bc 100644 --- a/cl/_testrt/asmfull/out.ll +++ b/cl/_testrt/asmfull/out.ll @@ -15,8 +15,9 @@ source_filename = "github.com/goplus/llgo/cl/_testrt/asmfull" @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 +@_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 define void @"github.com/goplus/llgo/cl/_testrt/asmfull.init"() { _llgo_0: @@ -34,12 +35,11 @@ _llgo_2: ; preds = %_llgo_1, %_llgo_0 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 + %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 @@ -47,6 +47,11 @@ _llgo_0: 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 + %10 = 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 %10) + call void @"github.com/goplus/llgo/runtime/internal/runtime.PrintByte"(i8 10) ret void } @@ -121,13 +126,13 @@ _llgo_5: ; preds = %_llgo_4 br label %_llgo_6 _llgo_6: ; preds = %_llgo_5, %_llgo_4 - %43 = load ptr, ptr @_llgo_uint32, align 8 + %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 42) - store ptr %45, ptr @_llgo_uint32, align 8 + %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 @@ -153,3 +158,9 @@ declare void @"github.com/goplus/llgo/runtime/internal/runtime.SetDirectIface"(p 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/instr.go b/cl/instr.go index 8bea9c25..5945eb56 100644 --- a/cl/instr.go +++ b/cl/instr.go @@ -116,8 +116,6 @@ func (p *context) asmFull(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { 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") @@ -147,8 +145,7 @@ func (p *context) asmFull(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { log.Printf("asmFull: %q -> %q, constraints: %q", asmString, finalAsm, constraintStr) } - // todo(zzy): call b.InlineAsmFull - return b.Const(constant.MakeInt64(0x1234), b.Prog.Uintptr()) + return b.InlineAsmFull(finalAsm, constraintStr, hasOutput, inputValues) } // ----------------------------------------------------------------------------- diff --git a/ssa/expr.go b/ssa/expr.go index 6f98cb38..8a34e6b6 100644 --- a/ssa/expr.go +++ b/ssa/expr.go @@ -286,6 +286,28 @@ func (b Builder) InlineAsm(instruction string) { b.impl.CreateCall(typ, asm, nil, "") } +func (b Builder) InlineAsmFull(instruction, constraints string, output bool, exprs []Expr) (ret Expr) { + var rtType llvm.Type + if output { + rtType = b.Prog.Uintptr().ll + } else { + rtType = b.Prog.tyVoid() + } + + typs := make([]llvm.Type, len(exprs)) + vals := make([]llvm.Value, len(exprs)) + for i, expr := range exprs { + typs[i] = expr.Type.ll + vals[i] = expr.impl + } + + typ := llvm.FunctionType(rtType, typs, false) + asm := llvm.InlineAsm(typ, instruction, constraints, true, false, llvm.InlineAsmDialectATT, false) + ret.Type = b.Prog.Uintptr() + ret.impl = b.impl.CreateCall(typ, asm, vals, "") + return +} + // GoString returns a Go string func (b Builder) GoString(v Expr) Expr { fn := b.Pkg.rtFunc("GoString") From 9e0c50dafe216c51c826452c4dee07157bb5b1ea Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Wed, 20 Aug 2025 23:06:11 +0800 Subject: [PATCH 04/16] cl/instr:asmFull test case with input only --- cl/_testrt/asmfull/in.go | 1 + cl/_testrt/asmfull/out.ll | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/cl/_testrt/asmfull/in.go b/cl/_testrt/asmfull/in.go index b17d87aa..473018ac 100644 --- a/cl/_testrt/asmfull/in.go +++ b/cl/_testrt/asmfull/in.go @@ -6,6 +6,7 @@ import _ "unsafe" func asmFull(instruction string, regs map[string]any) uintptr func main() { + asmFull("# test value {value}", map[string]any{"value": 42}) result := asmFull("mov {}, {value}", map[string]any{ "value": 42, }) diff --git a/cl/_testrt/asmfull/out.ll b/cl/_testrt/asmfull/out.ll index 1839d8bc..f83c2c2f 100644 --- a/cl/_testrt/asmfull/out.ll +++ b/cl/_testrt/asmfull/out.ll @@ -47,10 +47,21 @@ _llgo_0: 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 - %10 = call i64 asm sideeffect "mov $0, ${1}", "=&r,r"(i64 42) + 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 %10) + call void @"github.com/goplus/llgo/runtime/internal/runtime.PrintUint"(i64 %18) call void @"github.com/goplus/llgo/runtime/internal/runtime.PrintByte"(i8 10) ret void } From 6beb50b3676e80940d36f3b5cb5bac1c50b6095c Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Wed, 20 Aug 2025 23:43:25 +0800 Subject: [PATCH 05/16] cl/instr:asmFull test case with multiple input & output --- cl/_testrt/asmfull/in.go | 17 +++++++++++++++-- cl/_testrt/asmfull/out.ll | 26 ++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/cl/_testrt/asmfull/in.go b/cl/_testrt/asmfull/in.go index 473018ac..4bfa5933 100644 --- a/cl/_testrt/asmfull/in.go +++ b/cl/_testrt/asmfull/in.go @@ -6,9 +6,22 @@ import _ "unsafe" 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}) - result := asmFull("mov {}, {value}", map[string]any{ + // input with return value + res1 := asmFull("mov {}, {value}", map[string]any{ "value": 42, }) - println("Result:", result) + 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 index f83c2c2f..e7fcf275 100644 --- a/cl/_testrt/asmfull/out.ll +++ b/cl/_testrt/asmfull/out.ll @@ -18,6 +18,8 @@ source_filename = "github.com/goplus/llgo/cl/_testrt/asmfull" @_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: @@ -35,6 +37,7 @@ _llgo_2: ; preds = %_llgo_1, %_llgo_0 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 @@ -63,6 +66,29 @@ _llgo_0: 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 } From 9dfc6d1d52010f8ad117a3662a5f7d19be2d3175 Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Thu, 21 Aug 2025 00:15:59 +0800 Subject: [PATCH 06/16] ssa:refine asmFull interface --- cl/instr.go | 9 ++++++++- ssa/expr.go | 20 +++++--------------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/cl/instr.go b/cl/instr.go index 5945eb56..5ea09e9a 100644 --- a/cl/instr.go +++ b/cl/instr.go @@ -145,7 +145,14 @@ func (p *context) asmFull(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { log.Printf("asmFull: %q -> %q, constraints: %q", asmString, finalAsm, constraintStr) } - return b.InlineAsmFull(finalAsm, constraintStr, hasOutput, inputValues) + var retType llssa.Type + if hasOutput { + retType = b.Prog.Uintptr() + } else { + retType = b.Prog.Void() + } + + return b.InlineAsmFull(finalAsm, constraintStr, retType, inputValues) } // ----------------------------------------------------------------------------- diff --git a/ssa/expr.go b/ssa/expr.go index 8a34e6b6..b9dafca0 100644 --- a/ssa/expr.go +++ b/ssa/expr.go @@ -286,26 +286,16 @@ func (b Builder) InlineAsm(instruction string) { b.impl.CreateCall(typ, asm, nil, "") } -func (b Builder) InlineAsmFull(instruction, constraints string, output bool, exprs []Expr) (ret Expr) { - var rtType llvm.Type - if output { - rtType = b.Prog.Uintptr().ll - } else { - rtType = b.Prog.tyVoid() - } - +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] = expr.Type.ll - vals[i] = expr.impl + typs[i], vals[i] = expr.Type.ll, expr.impl } - typ := llvm.FunctionType(rtType, typs, false) - asm := llvm.InlineAsm(typ, instruction, constraints, true, false, llvm.InlineAsmDialectATT, false) - ret.Type = b.Prog.Uintptr() - ret.impl = b.impl.CreateCall(typ, asm, vals, "") - return + 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 From b428a8af0800da6e83022c89700e2f74295d389a Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Thu, 21 Aug 2025 11:04:03 +0800 Subject: [PATCH 07/16] test:asmFull function test --- _demo/asmfullcall/asmfullcall.go | 21 +++++++++++++++++ _demo/asmfullcall/asmfullcall_darwin.go | 31 +++++++++++++++++++++++++ _demo/asmfullcall/asmfullcall_linux.go | 4 ++++ 3 files changed, 56 insertions(+) create mode 100644 _demo/asmfullcall/asmfullcall.go create mode 100644 _demo/asmfullcall/asmfullcall_darwin.go create mode 100644 _demo/asmfullcall/asmfullcall_linux.go 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..5e537a21 --- /dev/null +++ b/_demo/asmfullcall/asmfullcall_darwin.go @@ -0,0 +1,31 @@ +//go:build darwin + +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..b0e60f75 --- /dev/null +++ b/_demo/asmfullcall/asmfullcall_linux.go @@ -0,0 +1,4 @@ +package main + +func verify() { +} From ef1f2bce49b3c25187b4c8d8490983d1b2a18c7c Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Thu, 21 Aug 2025 11:46:56 +0800 Subject: [PATCH 08/16] test:linux asmFull function test --- _demo/asmfullcall/asmfullcall_linux.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/_demo/asmfullcall/asmfullcall_linux.go b/_demo/asmfullcall/asmfullcall_linux.go index b0e60f75..8c485c63 100644 --- a/_demo/asmfullcall/asmfullcall_linux.go +++ b/_demo/asmfullcall/asmfullcall_linux.go @@ -1,4 +1,30 @@ +//go:build linux + 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("movq {a}, {}; addq {b}, {}", map[string]any{ + "a": 25, + "b": 17, + }) + check(42, int(res2)) } From d548671b91a0b723a0605c053b09fabe8e432a45 Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Thu, 21 Aug 2025 14:25:37 +0800 Subject: [PATCH 09/16] test:linux with leaq to confirm asmFull --- _demo/asmfullcall/asmfullcall_darwin.go | 2 +- _demo/asmfullcall/asmfullcall_linux.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/_demo/asmfullcall/asmfullcall_darwin.go b/_demo/asmfullcall/asmfullcall_darwin.go index 5e537a21..3f1fbcbd 100644 --- a/_demo/asmfullcall/asmfullcall_darwin.go +++ b/_demo/asmfullcall/asmfullcall_darwin.go @@ -1,4 +1,4 @@ -//go:build darwin +//go:build darwin && arm64 package main diff --git a/_demo/asmfullcall/asmfullcall_linux.go b/_demo/asmfullcall/asmfullcall_linux.go index 8c485c63..6a3f4a79 100644 --- a/_demo/asmfullcall/asmfullcall_linux.go +++ b/_demo/asmfullcall/asmfullcall_linux.go @@ -1,4 +1,4 @@ -//go:build linux +//go:build linux && amd64 package main @@ -22,7 +22,7 @@ func verify() { }) check(41, int(res1)) - res2 := asmFull("movq {a}, {}; addq {b}, {}", map[string]any{ + res2 := asmFull("leaq ({a},{b}), {}", map[string]any{ "a": 25, "b": 17, }) From 26fb156d6b59d8be2f4875a798daffb31af8bdfd Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Thu, 21 Aug 2025 16:30:52 +0800 Subject: [PATCH 10/16] cl/instr:only permit interger at asmfull --- cl/instr.go | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/cl/instr.go b/cl/instr.go index 5ea09e9a..82c3b620 100644 --- a/cl/instr.go +++ b/cl/instr.go @@ -133,9 +133,24 @@ func (p *context) asmFull(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { panic("asmFull: register not found: " + name) } if _, ok := registerNumbers[name]; !ok { - registerNumbers[name] = len(registerNumbers) - inputValues = append(inputValues, value) - constraints = append(constraints, "r") + // Type checking based on Go types - similar to TinyGo's implementation + rawType := value.Type.RawType() + switch typ := rawType.Underlying().(type) { + case *types.Basic: + if typ.Info()&types.IsInteger != 0 { + registerNumbers[name] = len(registerNumbers) + inputValues = append(inputValues, value) + constraints = append(constraints, "r") + } else { + panic("asmFull: unsupported basic type in inline assembly for operand: " + name + ", only integer types are supported") + } + case *types.Pointer: + // Pointer operands support was dropped, following TinyGo 0.23 + panic("asmFull: not support for pointer operands: " + name + ", only integer types are supported") + default: + panic("asmFull: unsupported type in inline assembly for operand: " + name + ", only integer types are supported") + } + } return fmt.Sprintf("${%v}", registerNumbers[name]) }) From f6bc5ac53834edeb111e3f91ac4bb7e9794c167e Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Thu, 21 Aug 2025 19:20:09 +0800 Subject: [PATCH 11/16] cl/instr:regexp compile one time --- cl/instr.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cl/instr.go b/cl/instr.go index 82c3b620..42c8ab9e 100644 --- a/cl/instr.go +++ b/cl/instr.go @@ -29,6 +29,8 @@ import ( llssa "github.com/goplus/llgo/ssa" ) +var asmRegisterRegex = regexp.MustCompile(`\{[a-zA-Z]+\}`) + // ----------------------------------------------------------------------------- func constStr(v ssa.Value) (ret string, ok bool) { @@ -124,7 +126,7 @@ func (p *context) asmFull(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { hasOutput = true } - finalAsm = regexp.MustCompile(`\{[a-zA-Z]+\}`).ReplaceAllStringFunc(finalAsm, func(s string) string { + 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] From 0faef117cac5ed6c0098362942f756ade360a5f8 Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Thu, 21 Aug 2025 19:57:49 +0800 Subject: [PATCH 12/16] cl/instr:refine register collect --- cl/instr.go | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/cl/instr.go b/cl/instr.go index 42c8ab9e..6f1221a3 100644 --- a/cl/instr.go +++ b/cl/instr.go @@ -97,18 +97,20 @@ func (p *context) asmFull(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { 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("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 + default: + panic("asmFull: don't know how to handle argument to inline assembly: " + r.String()) } } } @@ -135,24 +137,16 @@ func (p *context) asmFull(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { panic("asmFull: register not found: " + name) } if _, ok := registerNumbers[name]; !ok { - // Type checking based on Go types - similar to TinyGo's implementation + // Type checking - only allow integer basic types rawType := value.Type.RawType() - switch typ := rawType.Underlying().(type) { - case *types.Basic: - if typ.Info()&types.IsInteger != 0 { - registerNumbers[name] = len(registerNumbers) - inputValues = append(inputValues, value) - constraints = append(constraints, "r") - } else { - panic("asmFull: unsupported basic type in inline assembly for operand: " + name + ", only integer types are supported") - } - case *types.Pointer: + 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 0.23 - panic("asmFull: not support for pointer operands: " + name + ", only integer types are supported") - default: panic("asmFull: unsupported type in inline assembly for operand: " + name + ", only integer types are supported") } - } return fmt.Sprintf("${%v}", registerNumbers[name]) }) From 04f613dd154445cf43ec7bc5fbc9e8d5c0d28a86 Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Thu, 21 Aug 2025 20:55:20 +0800 Subject: [PATCH 13/16] cl/test:asmFull error --- cl/builtin_test.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) 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{ From d9dc4d5943236dfc7c5ccee6f321ce35b8e0e781 Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Thu, 21 Aug 2025 23:24:24 +0800 Subject: [PATCH 14/16] cl/instr:move to one asm --- cl/instr.go | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/cl/instr.go b/cl/instr.go index 6f1221a3..34e9c65a 100644 --- a/cl/instr.go +++ b/cl/instr.go @@ -72,24 +72,19 @@ func cstr(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { } // func asm(string) -// func asmFull(string, map[string]any) +// func asm(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 { - return p.asmFull(b, args) + if len(args) == 0 || len(args) > 2 { + panic("asm: invalid arguments - expected asm() or asm(, )") } - 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") + panic("asm: inline assembly requires a constant string") + } + if len(args) == 1 { + b.InlineAsm(asmString) + return llssa.Expr{Type: b.Prog.Void()} } registers := make(map[string]llssa.Expr) @@ -101,16 +96,16 @@ func (p *context) asmFull(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { // ignore case *ssa.MapUpdate: if r.Block() != registerMap.Block() { - panic("asmFull: register value map must be created in the same basic block") + panic("asm: 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") + panic("asm: register key must be a string constant") } llvmValue := p.compileValue(b, r.Value.(*ssa.MakeInterface).X) registers[key] = llvmValue default: - panic("asmFull: don't know how to handle argument to inline assembly: " + r.String()) + panic(fmt.Sprintf("asm: don't know how to handle argument to inline assembly: %s", r.String())) } } } @@ -134,7 +129,7 @@ func (p *context) asmFull(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { name := s[1 : len(s)-1] value, ok := registers[name] if !ok { - panic("asmFull: register not found: " + name) + panic(fmt.Sprintf("asm: register not found: %s", name)) } if _, ok := registerNumbers[name]; !ok { // Type checking - only allow integer basic types @@ -145,7 +140,7 @@ func (p *context) asmFull(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { constraints = append(constraints, "r") } else { // Pointer operands support was dropped, following TinyGo 0.23 - panic("asmFull: unsupported type in inline assembly for operand: " + name + ", only integer types are supported") + panic(fmt.Sprintf("asm: unsupported type in inline assembly for operand: %s, only integer types are supported", name)) } } return fmt.Sprintf("${%v}", registerNumbers[name]) @@ -153,7 +148,7 @@ func (p *context) asmFull(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { constraintStr := strings.Join(constraints, ",") if debugInstr { - log.Printf("asmFull: %q -> %q, constraints: %q", asmString, finalAsm, constraintStr) + log.Printf("asm: %q -> %q, constraints: %q", asmString, finalAsm, constraintStr) } var retType llssa.Type From 49b9b92790ba6541776720a70a3f6bf2a67955b5 Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Fri, 22 Aug 2025 15:01:59 +0800 Subject: [PATCH 15/16] ci/instr:fix asmFull return type to match function signature when no output --- cl/instr.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/cl/instr.go b/cl/instr.go index 34e9c65a..9a39b7e4 100644 --- a/cl/instr.go +++ b/cl/instr.go @@ -151,14 +151,13 @@ func (p *context) asm(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { log.Printf("asm: %q -> %q, constraints: %q", asmString, finalAsm, constraintStr) } - var retType llssa.Type - if hasOutput { - retType = b.Prog.Uintptr() - } else { - retType = b.Prog.Void() + 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, retType, inputValues) + return b.InlineAsmFull(finalAsm, constraintStr, b.Prog.Uintptr(), inputValues) } // ----------------------------------------------------------------------------- From f3de14da4ba488218657c6af727d70b7ea429831 Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Fri, 22 Aug 2025 16:14:51 +0800 Subject: [PATCH 16/16] cl/instr:note for why asm not support pointer type: --- cl/instr.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cl/instr.go b/cl/instr.go index 9a39b7e4..5dabd5f8 100644 --- a/cl/instr.go +++ b/cl/instr.go @@ -139,7 +139,8 @@ func (p *context) asm(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { inputValues = append(inputValues, value) constraints = append(constraints, "r") } else { - // Pointer operands support was dropped, following TinyGo 0.23 + // 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)) } }