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
|
||||
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=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=arm64 $(TINYGO) build -size short -o test ./testdata/cgo
|
||||
ifneq ($(OS),Windows_NT)
|
||||
|
|
|
@ -151,7 +151,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
|||
return BuildResult{}, err
|
||||
}
|
||||
unlock()
|
||||
libcDependencies = append(libcDependencies, makeMinGWExtraLibs(tmpdir)...)
|
||||
libcDependencies = append(libcDependencies, makeMinGWExtraLibs(tmpdir, config.GOARCH())...)
|
||||
case "":
|
||||
// no library specified, so nothing to do
|
||||
default:
|
||||
|
|
|
@ -59,6 +59,7 @@ func TestClangAttributes(t *testing.T) {
|
|||
{GOOS: "darwin", GOARCH: "amd64"},
|
||||
{GOOS: "darwin", GOARCH: "arm64"},
|
||||
{GOOS: "windows", GOARCH: "amd64"},
|
||||
{GOOS: "windows", GOARCH: "arm64"},
|
||||
} {
|
||||
name := "GOOS=" + options.GOOS + ",GOARCH=" + options.GOARCH
|
||||
if options.GOARCH == "arm" {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package builder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -43,7 +44,7 @@ var MinGW = Library{
|
|||
//
|
||||
// TODO: cache the result. At the moment, it costs a few hundred milliseconds to
|
||||
// compile these files.
|
||||
func makeMinGWExtraLibs(tmpdir string) []*compileJob {
|
||||
func makeMinGWExtraLibs(tmpdir, goarch string) []*compileJob {
|
||||
var jobs []*compileJob
|
||||
root := goenv.Get("TINYGOROOT")
|
||||
// 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,
|
||||
run: func(job *compileJob) error {
|
||||
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") {
|
||||
// .in files need to be preprocessed by a preprocessor (-E)
|
||||
// first.
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return link("ld.lld", "-m", "i386pep", "-o", outpath, defpath)
|
||||
return link("ld.lld", "-m", emulation, "-o", outpath, defpath)
|
||||
},
|
||||
}
|
||||
jobs = append(jobs, job)
|
||||
|
|
|
@ -28,7 +28,7 @@ const hasBuiltinTools = true
|
|||
func RunTool(tool string, args ...string) error {
|
||||
linker := "elf"
|
||||
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"
|
||||
} else if args[0] == "-flavor" {
|
||||
linker = args[1]
|
||||
|
|
|
@ -304,10 +304,19 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
|
|||
// normally present in Go (without explicitly opting in).
|
||||
// For more discussion:
|
||||
// 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,
|
||||
"-m", "i386pep",
|
||||
"-Bdynamic",
|
||||
"--image-base", "0x400000",
|
||||
"--gc-sections",
|
||||
"--no-insert-timestamp",
|
||||
"--no-dynamicbase",
|
||||
|
|
|
@ -162,9 +162,9 @@ mov x0, #0
|
|||
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}"
|
||||
if b.GOOS != "darwin" {
|
||||
if b.GOOS != "darwin" && b.GOOS != "windows" {
|
||||
// These registers cause the following warning when compiling for
|
||||
// MacOS:
|
||||
// MacOS and Windows:
|
||||
// warning: inline asm clobber list contains reserved registers:
|
||||
// X18, FP
|
||||
// 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
|
||||
|
||||
|
|
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
|
Загрузка…
Создание таблицы
Сослаться в новой задаче