Skip to content

feat: Add machine package validation workflow for TinyGo v0.39.0#26

Merged
xushiwei merged 7 commits into
goplus:mainfrom
luoliwoshang:adapt/tinygo-0.39.0-machine
Nov 30, 2025
Merged

feat: Add machine package validation workflow for TinyGo v0.39.0#26
xushiwei merged 7 commits into
goplus:mainfrom
luoliwoshang:adapt/tinygo-0.39.0-machine

Conversation

@luoliwoshang
Copy link
Copy Markdown
Member

@luoliwoshang luoliwoshang commented Nov 21, 2025

Summary

Sync machine package and related dependencies from TinyGo v0.39.0 to provide hardware abstraction layer support for LLGO.

TinyGo source reference: https://github.com/tinygo-org/tinygo/tree/v0.39.0/src/machine

Synced Content

Directory Description
machine/ Hardware abstraction layer code
runtime/interrupt/ Interrupt handling code
runtime/volatile/ Volatile memory operations
internal/binary/ Binary encoding/decoding (copied from TinyGo src/internal)
internal/bytealg/ Byte algorithms (copied from TinyGo src/internal)
internal/itoa/ Integer to string (copied from Go stdlib, as internal packages cannot be imported directly)

Supported Target Platforms

Machine package supports 90+ board targets, including:

  • Nordic nRF series: pca10040, pca10056, microbit, microbit-v2, etc.
  • Adafruit series: feather-m0, feather-m4, itsybitsy-m0/m4, circuitplay-express, etc.
  • Raspberry Pi series: pico, pico2, nano-rp2040, feather-rp2040, etc.
  • STM32 series: bluepill, stm32f4disco, nucleo-f103rb, nucleo-l476rg, etc.
  • ESP32 series: esp-c3-32s-kit, xiao-esp32c3, m5stamp-c3, etc.
  • Teensy series: teensy36, teensy40, teensy41
  • Others: wasm, wasm-unknown

See MACHINE_VALIDATION_TARGETS in .github/scripts/validate_embed_packages.sh for the complete list.


LLGO Adaptations

1. Import Path Adaptation

TinyGo uses relative paths, which need to be changed to full module paths.

machine/ directory

TinyGo goplus/emb
"device/*" "github.com/goplus/emb/device/*"
"runtime/*" "github.com/goplus/emb/runtime/*"
"internal/binary" "github.com/goplus/emb/internal/binary"
"internal/bytealg" "github.com/goplus/emb/internal/bytealg"
"internal/itoa" "github.com/goplus/emb/internal/itoa"

machine/usb/ directory

TinyGo goplus/emb
"machine" "github.com/goplus/emb/machine"
"machine/usb/*" "github.com/goplus/emb/machine/usb/*"
"runtime/*" "github.com/goplus/emb/runtime/*"

2. volatile Package Adaptation

runtime/volatile/volatile.go needs to declare LLGoPackage = "link":

const (
    LLGoPackage = "link"
)

3. CGO to LLGO wrap C Adaptation

TinyGo uses CGO to call C functions. LLGO doesn't support CGO, so we need to use LLGO's C wrapper mechanism.

Adaptation Steps

  1. Declare C file path: Use LLGoFiles constant in Go file to specify C file
const (
    LLGoFiles = "_wrap/machine_rp2040_rom.c"
)
  1. Use //go:linkname to link C functions: Link Go functions to C functions
// TinyGo CGO style (requires import "C"):
// C.reset_usb_boot(0, 0)
// C.flash_range_write(C.uint32_t(addr), (*C.uint8_t)(ptr), C.ulong(len))

// LLGO adaptation:
//go:linkname reset_usb_boot C.reset_usb_boot
func reset_usb_boot(c.Uint32T, c.Uint32T)

//go:linkname flash_range_write C.flash_range_write
func flash_range_write(c.Uint32T, *c.Uint8T, c.SizeT)
  1. Type mapping: Use types from github.com/goplus/lib/c package
CGO Type LLGO Type
C.uint8_t c.Uint8T
C.uint32_t c.Uint32T
C.ulong / C.size_t c.SizeT
  1. Write C wrapper files: Create corresponding .c files in _wrap/ directory

Related Files

Go File C Wrapper File Description
machine_rp2040_rom.go _wrap/machine_rp2040_rom.c RP2040 ROM functions
machine_rp2350_rom.go _wrap/machine_rp2050_rom.c RP2350 ROM functions

4. External Dependencies

Requires github.com/goplus/lib/c package for C language interop:

go get github.com/goplus/lib/c

5. Compiler Directive Placeholders

The following functions require LLGO compiler or runtime support, currently using panic or empty implementations as placeholders:

File Function Description
runtime/interrupt/interrupt.go:24 New() Interrupt creation mechanism
runtime/interrupt/checkpoint.go:39 Checkpoint.Save() Checkpoint save
machine/machine_avr.go:149 adjustMonotonicTimer() Requires runtime implementation
machine/machine_avr.go:153 initMonotonicTimer() Requires runtime implementation

Validation

CI Validation

./.github/scripts/validate_embed_packages.sh machine

Dependencies

This PR depends on PR #25 being merged first.

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello @luoliwoshang, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the TinyGo ecosystem by introducing a comprehensive validation workflow for machine packages, specifically targeting TinyGo v0.39.0. It expands hardware support to over 90 new board targets and refactors core device packages to better integrate with the LLGO compiler, moving towards more automated device file generation. This ensures greater stability and broader compatibility for embedded Go development.

Highlights

  • Machine Package Validation Workflow: A new GitHub Actions workflow (validate-machine-packages) has been introduced to validate TinyGo v0.39.0 machine packages, ensuring compatibility and functionality across a wide range of embedded targets.
  • Expanded Board Support: Over 90 new board targets have been added for machine package validation, including various Nordic nRF, Adafruit, Raspberry Pi, STM32, Teensy, and ESP32 boards, significantly broadening the supported hardware.
  • LLGO Integration & Refactoring: The device/arm and device/arm64 packages have been updated to remove direct C imports and replace them with llgo:link directives and panic("TODO: ...") stubs for SVCall functions, aligning with LLGO's internal handling of assembly and system calls.
  • Automated Device File Generation: New scripts (device-gen.sh, validate_embed_packages.sh) and updated generation tools (gen-device-avr, gen-device-svd) streamline the process of creating device-specific Go, linker, and assembly files, leading to the removal of many manual .ld and .s files for AVR targets.
  • LLGO Commit Hash Update: The LLGO commit hash has been updated to 2d80951e7d8f5823c1fa92c8a7cb5dd351a10e6a, reflecting the latest integration with the LLGO compiler.
  • Known Target Limitations: Specific targets (AVR, ESP32, RISC-V) are noted as disabled due to existing issues such as register allocation errors, binary size constraints, or missing picolibc symbols.
Ignored Files
  • Ignored by pattern: .github/workflows/** (2)
    • .github/workflows/scripts/sync_module.sh
    • .github/workflows/validate-embed-packages.yml
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@luoliwoshang luoliwoshang force-pushed the adapt/tinygo-0.39.0-machine branch from b2f9441 to 568ca14 Compare November 21, 2025 10:37
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a comprehensive machine package validation workflow for TinyGo v0.39.0, including new GitHub Actions, generation scripts, and validation scripts. It also involves a significant number of modifications across device files for ARM and AVR to ensure compatibility with the LLGO compiler. The changes are extensive but appear consistent and well-aligned with the goal of integrating with LLGO. My review focuses on improving the robustness of the new scripts and actions, and ensuring that unimplemented function stubs fail loudly to prevent silent errors.

Comment on lines +24 to +32
run: |
echo "Setting up LLGO from commit hash ${{ inputs.commit-hash }}..."
cd llgo-source
# Install LLGO from source
bash ./install.sh
# Set LLGO_ROOT to the source directory
echo "LLGO_ROOT=$PWD" >> $GITHUB_ENV
# Verify LLGO is available
llgo version || echo "Failed to get LLGO version"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The run script should be made more robust. It's good practice to have scripts fail immediately on any error by using set -e. Additionally, the current verification step for llgo version will not fail the workflow if llgo is not found or the command fails, which could lead to confusing errors in subsequent steps. It's better to fail early and clearly.

      run: |
        set -e
        echo "Setting up LLGO from commit hash ${{ inputs.commit-hash }}..."
        cd llgo-source
        # Install LLGO from source
        bash ./install.sh
        # Set LLGO_ROOT to the source directory
        echo "LLGO_ROOT=$PWD" >> $GITHUB_ENV
        # Verify LLGO is available
        llgo version

# Copy ignore list files to temporary directory
if [ -n "$ignore_list" ]; then
echo "[$name] Copying ignore list files to temporary directory: $ignore_list"
for file in $ignore_list; do
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

This loop over $ignore_list relies on word splitting and will not work correctly if any filename in the list contains spaces. While this is not an issue with the current ignore lists, it's a potential source of bugs in the future. To make the script more robust, I recommend defining ignore_list as a bash array and iterating over it properly.

For example, you could change the definitions from a string like ignore_list="file1.go file2.go" to an array: ignore_list=("file1.go" "file2.go").

Then, you can iterate over it safely like this:

for file in "${ignore_list[@]}"; do
    # ...
done

Comment thread device/arm/arm.go
func AsmFull(asm string, regs map[string]interface{}) uintptr
//
//llgo:link AsmFull llgo.asm
func AsmFull(asm string, regs map[string]interface{}) uintptr { return 0 }
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The AsmFull function is stubbed to return 0. This could lead to silent failures if it's called, as the caller might not realize it's an unimplemented function. It would be safer to make it panic, similar to the SVCall* functions, to clearly signal that it's a TODO.

Suggested change
func AsmFull(asm string, regs map[string]interface{}) uintptr { return 0 }
func AsmFull(asm string, regs map[string]interface{}) uintptr { panic("TODO: AsmFull") }

Comment thread device/arm64/arm64.go
func AsmFull(asm string, regs map[string]interface{}) uintptr
//
//llgo:link AsmFull llgo.asm
func AsmFull(asm string, regs map[string]interface{}) uintptr { return 0 }
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The AsmFull function is stubbed to return 0. This could lead to silent failures if it's called, as the caller might not realize it's an unimplemented function. It would be safer to make it panic, similar to the SVCall* functions, to clearly signal that it's a TODO.

Suggested change
func AsmFull(asm string, regs map[string]interface{}) uintptr { return 0 }
func AsmFull(asm string, regs map[string]interface{}) uintptr { panic("TODO: AsmFull") }

Comment thread device/asm.go
Comment on lines +25 to +27
func AsmFull(asm string, regs map[string]interface{}) uintptr {
return 0
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The AsmFull function is stubbed to return 0. This could lead to silent failures if it's called. It would be safer to make it panic to clearly indicate that it's not implemented.

Suggested change
func AsmFull(asm string, regs map[string]interface{}) uintptr {
return 0
}
func AsmFull(asm string, regs map[string]interface{}) uintptr {
panic("TODO: AsmFull")
}

echo "[$device_name] Processing ${#tasks_array[@]} tasks..."
for task in "${tasks_array[@]}"; do
echo "[$device_name] Processing task: $task"
"$generator" $task "$target_dir/"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[SECURITY - HIGH] Potential Command Injection Vulnerability

The $generator and $task variables are executed without validation. While the current configuration is safe, this creates a code execution vector if configurations are modified.

Recommendation:

# Validate generator name
if [[ ! "$generator" =~ ^(gen-device-avr|gen-device-svd)$ ]]; then
    echo "Error: Invalid generator: $generator"
    return 1
fi

Reference: CWE-78 (OS Command Injection)

# Clone and setup repository
echo "[$name] Setting up repository..."
rm -rf "$lib_path"
git clone "$repo" "$lib_path"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[SECURITY - HIGH] Path Traversal Vulnerability

The $lib_path and $target_dir variables are not validated before file operations. An attacker with repository access could specify paths like ../../.git to delete critical files.

Recommendation:

# Validate paths before use
validate_path() {
    local path="$1"
    if [[ "$path" =~ \.\. ]] || [[ "$path" =~ ^/ ]]; then
        echo "Error: Invalid path: $path"
        return 1
    fi
    return 0
}

validate_path "$lib_path" || return 1
validate_path "$target_dir" || return 1

Reference: CWE-22 (Path Traversal)

fmt.Println(infile)
device, err := readSVD(infile, sourceURL)
if err != nil {
return fmt.Errorf("failed to read: %w", err)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[PERFORMANCE - CRITICAL] Sequential File Processing

SVD files are processed sequentially in a single-threaded loop. This is significantly slower than the parallel approach used in gen-device-avr.go.

Impact: 2-5x slower than necessary for large device sets.

Recommendation: Implement parallel processing using worker goroutines similar to the AVR generator (lines 679-716 in gen-device-avr.go):

var wg sync.WaitGroup
workChan := make(chan string)
errChan := make(chan error, 1)
for i := 0; i < runtime.NumCPU(); i++ {
    go func() {
        for infile := range workChan {
            // Process file
        }
    }()
}

}

func main() {
indir := os.Args[1] // directory with register descriptor files (*.atdf)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[SECURITY - MEDIUM] Missing Input Validation

The tool directly uses command-line arguments without validation, causing panics on missing arguments.

Recommendation:

if len(os.Args) != 3 {
    fmt.Fprintln(os.Stderr, "Usage: gen-device-avr <input-dir> <output-dir>")
    os.Exit(1)
}

indir := filepath.Clean(os.Args[1])
outdir := filepath.Clean(os.Args[2])

// Validate no path traversal
if strings.Contains(indir, "..") || strings.Contains(outdir, "..") {
    fmt.Fprintln(os.Stderr, "Error: path traversal not allowed")
    os.Exit(1)
}

for package_spec in "${DEVICE_VALIDATION_TARGETS[@]}"; do
validate_device_package "$package_spec" || true
done

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[PERFORMANCE - HIGH] Sequential Validation

Validation tests run sequentially (~150 machine targets + 30 device targets), taking 15-60 minutes. With parallelization, this could be reduced to 3-10 minutes.

Recommendation: Use GNU parallel or bash background jobs:

# Parallel validation with GNU parallel
parallel -j $(nproc) validate_machine_target {} "$test_file" ::: "${MACHINE_VALIDATION_TARGETS[@]}"

# Or with bash background jobs and semaphore control
max_jobs=$(nproc)
for target in "${MACHINE_VALIDATION_TARGETS[@]}"; do
    while [ $(jobs -r | wc -l) -ge $max_jobs ]; do sleep 0.1; done
    validate_machine_target "$target" "$test_file" &
done
wait

@@ -0,0 +1,727 @@
// From tinygo v0.39.0
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[DOCUMENTATION] Missing Package Documentation

The file lacks proper package-level documentation explaining the tool's purpose and usage.

Recommendation:

// Package main provides gen-device-avr, a tool that generates Go device
// definitions from AVR microcontroller ATDF (Atmel Device File) files.
// This tool is based on TinyGo v0.39.0's device generation code.
//
// Usage:
//   gen-device-avr <input-directory> <output-directory>
//
// The input directory should contain .atdf files, and generated .go files
// will be written to the output directory.
package main

Comment thread device/arm/arm.go
func SVCall0(num uintptr) uintptr
func SVCall0(num uintptr) uintptr {
// TODO(zzy): implement system call (SVCall) functionality for ARM Cortex-M
// TinyGo compiler implementation: tinygo/compiler/inlineasm.go emitSVCall function
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[CODE QUALITY] Stubbed Functions with Panic

All 5 SVCall functions panic with TODO messages, making them impossible to test or use safely. The unreachable return 0 statement after panic() should be removed.

Recommendation:

func SVCall0(num uintptr) uintptr {
    // TODO(zzy): implement system call (SVCall) functionality for ARM Cortex-M
    // See: https://github.com/goplus/llgo/issues/XXXX
    panic("SVCall0 not yet implemented - see issue #XXXX")
}

Consider adding an issue reference and removing unreachable returns.

workChan := make(chan string)
errChan := make(chan error, 1)
for i := 0; i < runtime.NumCPU(); i++ {
go func() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[CODE QUALITY] Error Channel Race Condition

The error channel has a buffer size of 1, meaning only the first error is captured. If multiple workers encounter errors simultaneously, subsequent errors are silently dropped.

Recommendation:

// Buffer for all workers to prevent dropped errors
errChan := make(chan error, runtime.NumCPU())

// Or collect all errors:
var errMux sync.Mutex
var errors []error

// In worker:
if err != nil {
    errMux.Lock()
    errors = append(errors, err)
    errMux.Unlock()
}

@fennoai
Copy link
Copy Markdown
Contributor

fennoai Bot commented Nov 21, 2025

Comprehensive Code Review Summary

This PR adds substantial machine package validation infrastructure for TinyGo v0.39.0. The implementation is solid overall, but requires attention to security and performance concerns before merging.

Critical Issues (Must Fix)

Security:

  • Command injection and path traversal vulnerabilities in shell scripts
  • Missing input validation in Go tools

Performance:

  • Sequential SVD file processing (2-5x slower than necessary)
  • Sequential validation tests (could reduce CI time from 15-60min to 3-10min)

Positive Highlights

  • Excellent parallel processing in AVR generator
  • Comprehensive validation coverage (90+ board targets)
  • Clean separation of device generation and validation

Recommended Actions

  1. Add input validation and path sanitization to all scripts and tools
  2. Parallelize SVD file generation
  3. Parallelize validation tests
  4. Add package-level documentation

See inline comments for specific recommendations and code examples.

@luoliwoshang luoliwoshang force-pushed the adapt/tinygo-0.39.0-machine branch from 639c1b0 to f74b6a1 Compare November 21, 2025 11:47
luoliwoshang and others added 5 commits November 21, 2025 21:47
- Add validate-machine-packages job to workflow
- Add MACHINE_VALIDATION_TARGETS with 90+ board targets
- Update LLGO commit hash to 2d80951

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Sync machine package from goplus/lib PR goplus#19
- Update import paths from github.com/goplus/lib/emb to github.com/goplus/emb
- Add _wrap directory for LLGO wrapper functions
- Update all board and architecture specific files

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add runtime/interrupt package with architecture-specific implementations
- Add internal/binary, internal/bytealg, internal/itoa packages
- Update import paths to github.com/goplus/emb

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@luoliwoshang luoliwoshang force-pushed the adapt/tinygo-0.39.0-machine branch from c272e3e to 07f161b Compare November 21, 2025 13:48
@luoliwoshang luoliwoshang force-pushed the adapt/tinygo-0.39.0-machine branch from 561ef8f to 6dddf10 Compare November 28, 2025 06:55
@xushiwei xushiwei merged commit 38031d3 into goplus:main Nov 30, 2025
2 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.

2 participants