windows: add ARM64 support
This was actually surprising once I got TinyGo to build on Windows 11 ARM64. All the changes are exactly what you'd expect for a new architecture, there was no special weirdness just for arm64. Actually getting TinyGo to build was kind of involved though. The very short summary is: install arm64 versions of some pieces of software (like golang, cmake) instead of installing them though choco. In particular, use the llvm-mingw[1] toolchain instead of using standard mingw. [1]: https://github.com/mstorsjo/llvm-mingw/releases
Этот коммит содержится в:
родитель
45c8817ddd
коммит
df0f5ae1da
11 изменённых файлов: 206 добавлений и 10 удалений
1
Makefile
1
Makefile
|
@ -738,6 +738,7 @@ endif
|
||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
GOOS=linux GOARCH=arm $(TINYGO) build -size short -o test.elf ./testdata/cgo
|
GOOS=linux GOARCH=arm $(TINYGO) build -size short -o test.elf ./testdata/cgo
|
||||||
GOOS=windows GOARCH=amd64 $(TINYGO) build -size short -o test.exe ./testdata/cgo
|
GOOS=windows GOARCH=amd64 $(TINYGO) build -size short -o test.exe ./testdata/cgo
|
||||||
|
GOOS=windows GOARCH=arm64 $(TINYGO) build -size short -o test.exe ./testdata/cgo
|
||||||
GOOS=darwin GOARCH=amd64 $(TINYGO) build -size short -o test ./testdata/cgo
|
GOOS=darwin GOARCH=amd64 $(TINYGO) build -size short -o test ./testdata/cgo
|
||||||
GOOS=darwin GOARCH=arm64 $(TINYGO) build -size short -o test ./testdata/cgo
|
GOOS=darwin GOARCH=arm64 $(TINYGO) build -size short -o test ./testdata/cgo
|
||||||
ifneq ($(OS),Windows_NT)
|
ifneq ($(OS),Windows_NT)
|
||||||
|
|
|
@ -151,7 +151,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||||
return BuildResult{}, err
|
return BuildResult{}, err
|
||||||
}
|
}
|
||||||
unlock()
|
unlock()
|
||||||
libcDependencies = append(libcDependencies, makeMinGWExtraLibs(tmpdir)...)
|
libcDependencies = append(libcDependencies, makeMinGWExtraLibs(tmpdir, config.GOARCH())...)
|
||||||
case "":
|
case "":
|
||||||
// no library specified, so nothing to do
|
// no library specified, so nothing to do
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -59,6 +59,7 @@ func TestClangAttributes(t *testing.T) {
|
||||||
{GOOS: "darwin", GOARCH: "amd64"},
|
{GOOS: "darwin", GOARCH: "amd64"},
|
||||||
{GOOS: "darwin", GOARCH: "arm64"},
|
{GOOS: "darwin", GOARCH: "arm64"},
|
||||||
{GOOS: "windows", GOARCH: "amd64"},
|
{GOOS: "windows", GOARCH: "amd64"},
|
||||||
|
{GOOS: "windows", GOARCH: "arm64"},
|
||||||
} {
|
} {
|
||||||
name := "GOOS=" + options.GOOS + ",GOARCH=" + options.GOARCH
|
name := "GOOS=" + options.GOOS + ",GOARCH=" + options.GOARCH
|
||||||
if options.GOARCH == "arm" {
|
if options.GOARCH == "arm" {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package builder
|
package builder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -43,7 +44,7 @@ var MinGW = Library{
|
||||||
//
|
//
|
||||||
// TODO: cache the result. At the moment, it costs a few hundred milliseconds to
|
// TODO: cache the result. At the moment, it costs a few hundred milliseconds to
|
||||||
// compile these files.
|
// compile these files.
|
||||||
func makeMinGWExtraLibs(tmpdir string) []*compileJob {
|
func makeMinGWExtraLibs(tmpdir, goarch string) []*compileJob {
|
||||||
var jobs []*compileJob
|
var jobs []*compileJob
|
||||||
root := goenv.Get("TINYGOROOT")
|
root := goenv.Get("TINYGOROOT")
|
||||||
// Normally all the api-ms-win-crt-*.def files are all compiled to a single
|
// Normally all the api-ms-win-crt-*.def files are all compiled to a single
|
||||||
|
@ -74,16 +75,27 @@ func makeMinGWExtraLibs(tmpdir string) []*compileJob {
|
||||||
result: outpath,
|
result: outpath,
|
||||||
run: func(job *compileJob) error {
|
run: func(job *compileJob) error {
|
||||||
defpath := inpath
|
defpath := inpath
|
||||||
|
var archDef, emulation string
|
||||||
|
switch goarch {
|
||||||
|
case "amd64":
|
||||||
|
archDef = "-DDEF_X64"
|
||||||
|
emulation = "i386pep"
|
||||||
|
case "arm64":
|
||||||
|
archDef = "-DDEF_ARM64"
|
||||||
|
emulation = "arm64pe"
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported architecture for mingw-w64: %s", goarch)
|
||||||
|
}
|
||||||
if strings.HasSuffix(inpath, ".in") {
|
if strings.HasSuffix(inpath, ".in") {
|
||||||
// .in files need to be preprocessed by a preprocessor (-E)
|
// .in files need to be preprocessed by a preprocessor (-E)
|
||||||
// first.
|
// first.
|
||||||
defpath = outpath + ".def"
|
defpath = outpath + ".def"
|
||||||
err := runCCompiler("-E", "-x", "c", "-Wp,-w", "-P", "-DDEF_X64", "-DDATA", "-o", defpath, inpath, "-I"+goenv.Get("TINYGOROOT")+"/lib/mingw-w64/mingw-w64-crt/def-include/")
|
err := runCCompiler("-E", "-x", "c", "-Wp,-w", "-P", archDef, "-DDATA", "-o", defpath, inpath, "-I"+goenv.Get("TINYGOROOT")+"/lib/mingw-w64/mingw-w64-crt/def-include/")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return link("ld.lld", "-m", "i386pep", "-o", outpath, defpath)
|
return link("ld.lld", "-m", emulation, "-o", outpath, defpath)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
jobs = append(jobs, job)
|
jobs = append(jobs, job)
|
||||||
|
|
|
@ -28,7 +28,7 @@ const hasBuiltinTools = true
|
||||||
func RunTool(tool string, args ...string) error {
|
func RunTool(tool string, args ...string) error {
|
||||||
linker := "elf"
|
linker := "elf"
|
||||||
if tool == "ld.lld" && len(args) >= 2 {
|
if tool == "ld.lld" && len(args) >= 2 {
|
||||||
if args[0] == "-m" && args[1] == "i386pep" {
|
if args[0] == "-m" && (args[1] == "i386pep" || args[1] == "arm64pe") {
|
||||||
linker = "mingw"
|
linker = "mingw"
|
||||||
} else if args[0] == "-flavor" {
|
} else if args[0] == "-flavor" {
|
||||||
linker = args[1]
|
linker = args[1]
|
||||||
|
|
|
@ -304,10 +304,19 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
|
||||||
// normally present in Go (without explicitly opting in).
|
// normally present in Go (without explicitly opting in).
|
||||||
// For more discussion:
|
// For more discussion:
|
||||||
// https://groups.google.com/g/Golang-nuts/c/Jd9tlNc6jUE/m/Zo-7zIP_m3MJ?pli=1
|
// https://groups.google.com/g/Golang-nuts/c/Jd9tlNc6jUE/m/Zo-7zIP_m3MJ?pli=1
|
||||||
|
switch goarch {
|
||||||
|
case "amd64":
|
||||||
|
spec.LDFlags = append(spec.LDFlags,
|
||||||
|
"-m", "i386pep",
|
||||||
|
"--image-base", "0x400000",
|
||||||
|
)
|
||||||
|
case "arm64":
|
||||||
|
spec.LDFlags = append(spec.LDFlags,
|
||||||
|
"-m", "arm64pe",
|
||||||
|
)
|
||||||
|
}
|
||||||
spec.LDFlags = append(spec.LDFlags,
|
spec.LDFlags = append(spec.LDFlags,
|
||||||
"-m", "i386pep",
|
|
||||||
"-Bdynamic",
|
"-Bdynamic",
|
||||||
"--image-base", "0x400000",
|
|
||||||
"--gc-sections",
|
"--gc-sections",
|
||||||
"--no-insert-timestamp",
|
"--no-insert-timestamp",
|
||||||
"--no-dynamicbase",
|
"--no-dynamicbase",
|
||||||
|
|
|
@ -162,9 +162,9 @@ mov x0, #0
|
||||||
1:
|
1:
|
||||||
`
|
`
|
||||||
constraints = "={x0},{x1},~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7},~{x8},~{x9},~{x10},~{x11},~{x12},~{x13},~{x14},~{x15},~{x16},~{x17},~{x19},~{x20},~{x21},~{x22},~{x23},~{x24},~{x25},~{x26},~{x27},~{x28},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{q16},~{q17},~{q18},~{q19},~{q20},~{q21},~{q22},~{q23},~{q24},~{q25},~{q26},~{q27},~{q28},~{q29},~{q30},~{nzcv},~{ffr},~{vg},~{memory}"
|
constraints = "={x0},{x1},~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7},~{x8},~{x9},~{x10},~{x11},~{x12},~{x13},~{x14},~{x15},~{x16},~{x17},~{x19},~{x20},~{x21},~{x22},~{x23},~{x24},~{x25},~{x26},~{x27},~{x28},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{q16},~{q17},~{q18},~{q19},~{q20},~{q21},~{q22},~{q23},~{q24},~{q25},~{q26},~{q27},~{q28},~{q29},~{q30},~{nzcv},~{ffr},~{vg},~{memory}"
|
||||||
if b.GOOS != "darwin" {
|
if b.GOOS != "darwin" && b.GOOS != "windows" {
|
||||||
// These registers cause the following warning when compiling for
|
// These registers cause the following warning when compiling for
|
||||||
// MacOS:
|
// MacOS and Windows:
|
||||||
// warning: inline asm clobber list contains reserved registers:
|
// warning: inline asm clobber list contains reserved registers:
|
||||||
// X18, FP
|
// X18, FP
|
||||||
// Reserved registers on the clobber list may not be preserved
|
// Reserved registers on the clobber list may not be preserved
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build scheduler.tasks && arm64
|
//go:build scheduler.tasks && arm64 && !windows
|
||||||
|
|
||||||
package task
|
package task
|
||||||
|
|
||||||
|
|
65
src/internal/task/task_stack_arm64_windows.S
Обычный файл
65
src/internal/task/task_stack_arm64_windows.S
Обычный файл
|
@ -0,0 +1,65 @@
|
||||||
|
.section .text.tinygo_startTask,"ax"
|
||||||
|
.global tinygo_startTask
|
||||||
|
tinygo_startTask:
|
||||||
|
.cfi_startproc
|
||||||
|
// Small assembly stub for starting a goroutine. This is already run on the
|
||||||
|
// new stack, with the callee-saved registers already loaded.
|
||||||
|
// Most importantly, x19 contains the pc of the to-be-started function and
|
||||||
|
// x20 contains the only argument it is given. Multiple arguments are packed
|
||||||
|
// into one by storing them in a new allocation.
|
||||||
|
|
||||||
|
// Indicate to the unwinder that there is nothing to unwind, this is the
|
||||||
|
// root frame. It avoids the following (bogus) error message in GDB:
|
||||||
|
// Backtrace stopped: previous frame identical to this frame (corrupt stack?)
|
||||||
|
.cfi_undefined lr
|
||||||
|
|
||||||
|
// Set the first argument of the goroutine start wrapper, which contains all
|
||||||
|
// the arguments.
|
||||||
|
mov x0, x20
|
||||||
|
|
||||||
|
// Branch to the "goroutine start" function. By using blx instead of bx,
|
||||||
|
// we'll return here instead of tail calling.
|
||||||
|
blr x19
|
||||||
|
|
||||||
|
// After return, exit this goroutine. This is a tail call.
|
||||||
|
b tinygo_pause
|
||||||
|
.cfi_endproc
|
||||||
|
|
||||||
|
|
||||||
|
.global tinygo_swapTask
|
||||||
|
tinygo_swapTask:
|
||||||
|
// This function gets the following parameters:
|
||||||
|
// x0 = newStack uintptr
|
||||||
|
// x1 = oldStack *uintptr
|
||||||
|
|
||||||
|
// Save all callee-saved registers:
|
||||||
|
stp x19, x20, [sp, #-160]!
|
||||||
|
stp x21, x22, [sp, #16]
|
||||||
|
stp x23, x24, [sp, #32]
|
||||||
|
stp x25, x26, [sp, #48]
|
||||||
|
stp x27, x28, [sp, #64]
|
||||||
|
stp x29, x30, [sp, #80]
|
||||||
|
stp d8, d9, [sp, #96]
|
||||||
|
stp d10, d11, [sp, #112]
|
||||||
|
stp d12, d13, [sp, #128]
|
||||||
|
stp d14, d15, [sp, #144]
|
||||||
|
|
||||||
|
// Save the current stack pointer in oldStack.
|
||||||
|
mov x8, sp
|
||||||
|
str x8, [x1]
|
||||||
|
|
||||||
|
// Switch to the new stack pointer.
|
||||||
|
mov sp, x0
|
||||||
|
|
||||||
|
// Restore stack state and return.
|
||||||
|
ldp d14, d15, [sp, #144]
|
||||||
|
ldp d12, d13, [sp, #128]
|
||||||
|
ldp d10, d11, [sp, #112]
|
||||||
|
ldp d8, d9, [sp, #96]
|
||||||
|
ldp x29, x30, [sp, #80]
|
||||||
|
ldp x27, x28, [sp, #64]
|
||||||
|
ldp x25, x26, [sp, #48]
|
||||||
|
ldp x23, x24, [sp, #32]
|
||||||
|
ldp x21, x22, [sp, #16]
|
||||||
|
ldp x19, x20, [sp], #160
|
||||||
|
ret
|
72
src/internal/task/task_stack_arm64_windows.go
Обычный файл
72
src/internal/task/task_stack_arm64_windows.go
Обычный файл
|
@ -0,0 +1,72 @@
|
||||||
|
//go:build scheduler.tasks && arm64 && windows
|
||||||
|
|
||||||
|
package task
|
||||||
|
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
|
var systemStack uintptr
|
||||||
|
|
||||||
|
// calleeSavedRegs is the list of registers that must be saved and restored
|
||||||
|
// when switching between tasks. Also see task_stack_arm64_windows.S that
|
||||||
|
// relies on the exact layout of this struct.
|
||||||
|
type calleeSavedRegs struct {
|
||||||
|
x19 uintptr
|
||||||
|
x20 uintptr
|
||||||
|
x21 uintptr
|
||||||
|
x22 uintptr
|
||||||
|
x23 uintptr
|
||||||
|
x24 uintptr
|
||||||
|
x25 uintptr
|
||||||
|
x26 uintptr
|
||||||
|
x27 uintptr
|
||||||
|
x28 uintptr
|
||||||
|
x29 uintptr
|
||||||
|
pc uintptr // aka x30 aka LR
|
||||||
|
|
||||||
|
d8 uintptr
|
||||||
|
d9 uintptr
|
||||||
|
d10 uintptr
|
||||||
|
d11 uintptr
|
||||||
|
d12 uintptr
|
||||||
|
d13 uintptr
|
||||||
|
d14 uintptr
|
||||||
|
d15 uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// archInit runs architecture-specific setup for the goroutine startup.
|
||||||
|
func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) {
|
||||||
|
// Store the initial sp for the startTask function (implemented in assembly).
|
||||||
|
s.sp = uintptr(unsafe.Pointer(r))
|
||||||
|
|
||||||
|
// Initialize the registers.
|
||||||
|
// These will be popped off of the stack on the first resume of the goroutine.
|
||||||
|
|
||||||
|
// Start the function at tinygo_startTask (defined in src/internal/task/task_stack_arm64_windows.S).
|
||||||
|
// This assembly code calls a function (passed in x19) with a single argument
|
||||||
|
// (passed in x20). After the function returns, it calls Pause().
|
||||||
|
r.pc = uintptr(unsafe.Pointer(&startTask))
|
||||||
|
|
||||||
|
// Pass the function to call in x19.
|
||||||
|
// This function is a compiler-generated wrapper which loads arguments out of a struct pointer.
|
||||||
|
// See createGoroutineStartWrapper (defined in compiler/goroutine.go) for more information.
|
||||||
|
r.x19 = fn
|
||||||
|
|
||||||
|
// Pass the pointer to the arguments struct in x20.
|
||||||
|
r.x20 = uintptr(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) resume() {
|
||||||
|
swapTask(s.sp, &systemStack)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) pause() {
|
||||||
|
newStack := systemStack
|
||||||
|
systemStack = 0
|
||||||
|
swapTask(newStack, &s.sp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SystemStack returns the system stack pointer when called from a task stack.
|
||||||
|
// When called from the system stack, it returns 0.
|
||||||
|
func SystemStack() uintptr {
|
||||||
|
return systemStack
|
||||||
|
}
|
36
src/runtime/asm_arm64_windows.S
Обычный файл
36
src/runtime/asm_arm64_windows.S
Обычный файл
|
@ -0,0 +1,36 @@
|
||||||
|
.section .text.tinygo_scanCurrentStack,"ax"
|
||||||
|
.global tinygo_scanCurrentStack
|
||||||
|
tinygo_scanCurrentStack:
|
||||||
|
// Sources:
|
||||||
|
// * https://learn.microsoft.com/en-us/cpp/build/arm64-windows-abi-conventions?view=msvc-170
|
||||||
|
// * https://godbolt.org/z/foc1xncvb
|
||||||
|
|
||||||
|
// Save callee-saved registers.
|
||||||
|
stp x29, x30, [sp, #-160]!
|
||||||
|
stp x28, x27, [sp, #16]
|
||||||
|
stp x26, x25, [sp, #32]
|
||||||
|
stp x24, x23, [sp, #48]
|
||||||
|
stp x22, x21, [sp, #64]
|
||||||
|
stp x20, x19, [sp, #80]
|
||||||
|
stp d8, d9, [sp, #96]
|
||||||
|
stp d10, d11, [sp, #112]
|
||||||
|
stp d12, d13, [sp, #128]
|
||||||
|
stp d14, d15, [sp, #144]
|
||||||
|
|
||||||
|
// Scan the stack.
|
||||||
|
mov x0, sp
|
||||||
|
bl tinygo_scanstack
|
||||||
|
|
||||||
|
// Restore stack state and return.
|
||||||
|
ldp x29, x30, [sp], #160
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
.section .text.tinygo_longjmp,"ax"
|
||||||
|
.global tinygo_longjmp
|
||||||
|
tinygo_longjmp:
|
||||||
|
// Note: the code we jump to assumes x0 is set to a non-zero value if we
|
||||||
|
// jump from here (which is conveniently already the case).
|
||||||
|
ldp x1, x2, [x0] // jumpSP, jumpPC
|
||||||
|
mov sp, x1
|
||||||
|
br x2
|
Загрузка…
Создание таблицы
Сослаться в новой задаче