Building Go packages for Windows on ARM
Tags: go, cgo, windowsonarm
Published: 2024-06-16 21:59:09

## Intro

Recently, Qualcomm announced [list of laptops](https://www.qualcomm.com/products/mobile/snapdragon/pcs-and-tablets/laptop-device-finder) that will have their new Snapdragon X Elite chip which claim to have the same performance as Apple's M3 processor.

This means that in a near future, market will see more and more Windows on ARM laptops and we as developers should be prepared to that.

This article is structured in Q&A sections to briefly explain the topic.

{%- # TOC start -%}

- [WoA can emulate x86, isn't it?](#woa-can-emulate-x86-isnt-it)
- [Doesn't Go already support cross compilation?](#doesnt-go-already-support-cross-compilation)
- [Okay, isn't MinGW already cover that?](#okay-isnt-mingw-already-cover-that)
- [So how do I build a program with CGO for WoA?](#so-how-do-i-build-a-program-with-cgo-for-woa)
- [Is there a more convenient way to use llvm-mingw?](#is-there-a-more-convenient-way-to-use-llvmmingw)
- [How to use it?](#how-to-use-it)
   * [Result](#result)
   * [How can I integrate this into my CI?](#how-can-i-integrate-this-into-my-ci)

{%- # TOC end -%}


## WoA can emulate x86, isn't it?

Although Windows on ARM (WoA) supports x86 and x86-64 emulation, some features like vector instructions (like AVX2) might be not supported, also emulation brings some performance penalty.

People who use libraries like [simdjson](https://github.com/minio/simdjson-go) might be at risk.

## Doesn't Go already support cross compilation?

Go offers out-of-box cross compilation support, including ARM64 for Windows, but this doesn't cover CGO.

That means, any program that uses CGO libraries like [sqlite3](https://github.com/mattn/go-sqlite3) will require C and/or C++ toolchain for cross-compilation.

## Okay, isn't MinGW already cover that?

Most popular solution for cross-compilation from Windows and Linux is MinGW.

Unfortunately, MinGW doesn't support ARM64 target.

[MSYS2](https://www.msys2.org/wiki/arm64/) toolchain provide ARM support but available only for Windows.

That means that in order to build programs for Windows on ARM on a Linux machine, a different option should be considered as MinGW doesn't support arm64 target.

## So how do I build a program with CGO for WoA?

Don't worry, this case is already covered.

There is a llvm-based alternative toolchain that supports WoA - [llvm-mingw](https://github.com/mstorsjo/llvm-mingw).

Unfortunately it's not available in most distros except [Arch Linux](https://aur.archlinux.org/packages/mingw-w64-llvm), but you still can download prebuilt binaries from releases page.

## Is there a more convenient way to use llvm-mingw?

Yes, sure!

There is a special [Docker image](github.com/x1unix/docker-go-mingw/) with toolchain for all Windows architectures - amd64, x86 and **arm64**.

Image is called `x1unix/go-mingw` and offers arm64 target support since go `1.21`.

Image uses MinGW for x86 and llvm-mingw for arm64 target.

## How to use it?

It's quite simple, just pull the image and call `go build` command with `GOARCH=arm64` environment variable.

For example, let's take a simple WinAPI CGO example from [here](https://github.com/x1unix/docker-go-mingw/tree/master/example/hello) and build it using Docker image:

{% spoiler Go code %}
```go
package main

/*
#cgo LDFLAGS: -lkernel32
#include <windows.h>
#include <stdio.h>

// Function to show a MessageBox using WinAPI
void hello() {
  SYSTEM_INFO si;
  ZeroMemory( & si, sizeof(SYSTEM_INFO));
  GetSystemInfo( & si);
  char * arch;
  switch (si.wProcessorArchitecture) {
  case PROCESSOR_ARCHITECTURE_AMD64:
    arch = "AMD64";
    break;
  case PROCESSOR_ARCHITECTURE_INTEL:
    arch = "x86";
    break;
  case PROCESSOR_ARCHITECTURE_ARM:
    arch = "ARM";
    break;
  case PROCESSOR_ARCHITECTURE_ARM64:
    arch = "ARM64";
    break;
  case PROCESSOR_ARCHITECTURE_IA64:
    arch = "IA";
    break;
  default:
    arch = "Unknown";
    break;
  }

  char message[30];
  sprintf(message, "Hello from CGO on %s", arch);

  MessageBox(NULL, message, "Hello World", MB_OK);
}
*/
import "C"
import "fmt"

func main() {
	fmt.Println("Calling C function to open a MessageBox...")
	C.hello()
}
```
{% endspoiler %}


```shell
# Go version to use. WoA supported since Go 1.21.
export GO_VERSION=1.22

docker run --rm -it -e GOARCH=arm64 \
	-v .:/go/work -w /go/work \
	x1unix/go-mingw:$GO_VERSION \
	go build -o hello.exe .
```

### Result

After building a program, let's try to run it inside any VM with Windows on Arm.

Parallels Workstation is used for this example.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8f7pzmjw3d90ig1a7wk7.png)

### How can I integrate this into my CI?

As this is a plain Docker image, it can be easily used both in GitHub Actions and Gitlab CI.

Please check CI templates [here](https://github.com/x1unix/docker-go-mingw/tree/master/example/ci).