Skip to content

cl(feat): llgo.asm implement tinygo.AsmFull#1224

Merged
xushiwei merged 16 commits intogoplus:mainfrom
luoliwoshang:instr/asmfull
Aug 28, 2025
Merged

cl(feat): llgo.asm implement tinygo.AsmFull#1224
xushiwei merged 16 commits intogoplus:mainfrom
luoliwoshang:instr/asmfull

Conversation

@luoliwoshang
Copy link
Member

@luoliwoshang luoliwoshang commented Aug 20, 2025

resolved #1218

This PR fully adapts TinyGo's device.AsmFull implementation to provide LLGo with the same inline assembly capabilities, identical semantics, and constraint rules.
logic from tinygo/compiler/inlineasm

usage

//llgo:link asmFull llgo.asm
func asmFull(instruction string, regs map[string]any) uintptr { return 0 }

Parameters

  • instruction: Assembly template with placeholders ({} for output, {name} for inputs)
  • regs: Register mapping (nil for simple instructions, map[string]any for register values)
Example
asmFull("nop", nil)
  • Read Stack Pointer
 pc := asmFull("mov {}, sp", nil)
  • Memory Operation: Direct memory write
addr := uintptr(unsafe.Pointer(&testVar))
  asmFull("str {value}, [{addr}]", map[string]any{
      "addr":  addr,
      "value": 43,
  })

also is aims to provide TinyGo memory operations for embedded systems. Enables precise hardware register access and memory control, similar to TinyGo's device.AsmFull used in GPIO and peripheral programming.like follow operate

	riscv.AsmFull(
		"amoor.w {}, {mask}, ({reg})",
		map[string]interface{}{
			"mask": uint32(1 << pin),
			"reg":  uintptr(unsafe.Pointer(&kendryte.GPIOHS.FALL_IP.Reg)),
		})

tasks

  • return register
  • placeholder replace
  • llvm constraints
    test
  • ir compare test
  • error case unit test
  • function test [_demo/asmfullcall/asmfullcall.go]
    • macos
    • linux

Maps used solely at compile time (like those for instruction signature compilation) currently still introduce runtime map dependencies, causing failures on embedded boards. we maybe can implement compile-time analysis to detect when programs only use compile-time function signatures, and exclude runtime capabilities in such cases.

@luoliwoshang luoliwoshang marked this pull request as draft August 20, 2025 09:14
@luoliwoshang luoliwoshang changed the title cl(feat): llgo.asm implement tinygo.AsmFul [wip] cl(feat): llgo.asm implement tinygo.AsmFul Aug 20, 2025
@codecov
Copy link

codecov bot commented Aug 20, 2025

Codecov Report

❌ Patch coverage is 92.10526% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.19%. Comparing base (6de3bdc) to head (f3de14d).
⚠️ Report is 78 commits behind head on main.

Files with missing lines Patch % Lines
cl/instr.go 91.04% 4 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1224      +/-   ##
==========================================
+ Coverage   88.15%   88.19%   +0.03%     
==========================================
  Files          34       34              
  Lines        8470     8539      +69     
==========================================
+ Hits         7467     7531      +64     
- Misses        934      937       +3     
- Partials       69       71       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@luoliwoshang luoliwoshang force-pushed the instr/asmfull branch 4 times, most recently from 763cb8a to 29bd0c4 Compare August 20, 2025 13:02
@luoliwoshang luoliwoshang changed the title [wip] cl(feat): llgo.asm implement tinygo.AsmFul [wip] cl(feat): llgo.asm implement tinygo.AsmFull Aug 20, 2025
@luoliwoshang luoliwoshang force-pushed the instr/asmfull branch 3 times, most recently from 22a0445 to b31ef79 Compare August 20, 2025 14:44
@luoliwoshang luoliwoshang force-pushed the instr/asmfull branch 3 times, most recently from dbf7ead to 0d2655e Compare August 21, 2025 04:14
cl/instr.go Outdated
hasOutput = true
}

finalAsm = regexp.MustCompile(`\{[a-zA-Z]+\}`).ReplaceAllStringFunc(finalAsm, func(s string) string {
Copy link
Contributor

@MeteorsLiu MeteorsLiu Aug 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NOTE(MeteorsLiu): MustCompile can be optimized by once.Do() to avoid compiling regexp repeatedly.

Copy link
Member Author

@luoliwoshang luoliwoshang Aug 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 068253d but once.Do() maybe isn't necessary here since Go runtime already guarantees package-level variables are initialized only once during package initialization

@luoliwoshang luoliwoshang force-pushed the instr/asmfull branch 2 times, most recently from fcda0ea to 812fa38 Compare August 21, 2025 12:22
@luoliwoshang luoliwoshang changed the title [wip] cl(feat): llgo.asm implement tinygo.AsmFull cl(feat): llgo.asm implement tinygo.AsmFull Aug 21, 2025
@luoliwoshang luoliwoshang changed the title cl(feat): llgo.asm implement tinygo.AsmFull [wip] cl(feat): llgo.asm implement tinygo.AsmFull Aug 21, 2025
@luoliwoshang luoliwoshang marked this pull request as ready for review August 21, 2025 12:56
@luoliwoshang luoliwoshang changed the title [wip] cl(feat): llgo.asm implement tinygo.AsmFull cl(feat): llgo.asm implement tinygo.AsmFull Aug 21, 2025
%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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove runtime dependency in another PR

case *ssa.DebugRef, *ssa.Call:
// ignore
case *ssa.MapUpdate:
if r.Block() != registerMap.Block() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

Copy link
Member Author

@luoliwoshang luoliwoshang Aug 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this panic logic is fully consistent with current TinyGo behavior, and tracing back to the earliest commit of this code (tinygo-org/tinygo@392bba8), no reasoning was provided.
However, I believe the fundamental technical reason is that LLVM inline assembly requires all call information to be determined at compile time - parameter types, constraint strings, and parameter counts cannot change dynamically at runtime.

call i64 asm sideeffect "mov $0, ${1}", "=&r,r"(i64 42)

This is essentially the same requirement as any function call needing determined function signatures and parameter types at compile time.

If cross-basic-block map construction were allowed, the compiler couldn't determine what parameters are needed for the call:

regs := make(map[string]interface{})
  if condition {
      regs["value"] = 42
  }
res1 := asmFull("mov {}, {value}", regs)  // Compiler can't determine if value exists 

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

at this case *ssa.MapUpdate: we actually can't know if this value needs collecting at compile time.

asmFull("nop", nil)

// 0 output & 1 input with memory address
addr := uintptr(unsafe.Pointer(&testVar))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why pointer type is not supported?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current logic is fully corespoding with Tinygo,so panic with same case.
And the reason TinyGo currently doesn't support pointer operands in inline assembly is due to LLVM's transition from typed pointers to opaque pointers starting with LLVM 14 (https://llvm.org/docs/OpaquePointers.html). This change requires inline assembly to explicitly declare pointer element types using the elementtype attribute (https://reviews.llvm.org/D116531). Rather than implementing the additional complexity, TinyGo chose to remove pointer support entirely (tinygo-org/tinygo@cad6a57).

However, I believe it's possible to add back this pointer type support. As TinyGo's comment indicates, it would require modifying the go-llvm API. I think we should make this functionality available first, and then support this feature in a separate PR (after all, going from uintptr(unsafe.Pointer(&val)) to &val wouldn't introduce breaking changes).

} else {
retType = b.Prog.Void()
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is retType matched with the func return type?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed at 49b9b92, Now when hasOutput is false, we return a valid uintptr(0) value that matches the Go function signature, rather than returning a void-typed inline assembly call.

@luoliwoshang
Copy link
Member Author

@xushiwei need review

@xushiwei xushiwei merged commit 6144987 into goplus:main Aug 28, 2025
34 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cl(feat): llgo.asm implement tinygo.AsmFull

4 participants