aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAyke van Laethem <[email protected]>2023-01-29 03:33:37 -0800
committerRon Evans <[email protected]>2023-01-30 21:42:47 +0100
commitdf0f5ae1da0fdb7b136e70ca9bd1e3217c04dbcd (patch)
treed9156f0ebe67d883f9fe2195d48cf9b183fd39e4
parent45c8817ddd6818ec96d4f180dc15237e945e5122 (diff)
downloadtinygo-df0f5ae1da0fdb7b136e70ca9bd1e3217c04dbcd.tar.gz
tinygo-df0f5ae1da0fdb7b136e70ca9bd1e3217c04dbcd.zip
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
-rw-r--r--Makefile1
-rw-r--r--builder/build.go2
-rw-r--r--builder/builder_test.go1
-rw-r--r--builder/mingw-w64.go18
-rw-r--r--builder/tools-builtin.go2
-rw-r--r--compileopts/target.go13
-rw-r--r--compiler/defer.go4
-rw-r--r--src/internal/task/task_stack_arm64.go2
-rw-r--r--src/internal/task/task_stack_arm64_windows.S65
-rw-r--r--src/internal/task/task_stack_arm64_windows.go72
-rw-r--r--src/runtime/asm_arm64_windows.S36
11 files changed, 206 insertions, 10 deletions
diff --git a/Makefile b/Makefile
index 97ba293cb..73c25806c 100644
--- a/Makefile
+++ b/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)
diff --git a/builder/build.go b/builder/build.go
index 26395d53f..78a9dc662 100644
--- a/builder/build.go
+++ b/builder/build.go
@@ -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:
diff --git a/builder/builder_test.go b/builder/builder_test.go
index 3b0ae8bcf..f8bdd5134 100644
--- a/builder/builder_test.go
+++ b/builder/builder_test.go
@@ -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" {
diff --git a/builder/mingw-w64.go b/builder/mingw-w64.go
index 289a85489..1e7701d47 100644
--- a/builder/mingw-w64.go
+++ b/builder/mingw-w64.go
@@ -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)
diff --git a/builder/tools-builtin.go b/builder/tools-builtin.go
index fbe6ef02f..2b3cba618 100644
--- a/builder/tools-builtin.go
+++ b/builder/tools-builtin.go
@@ -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]
diff --git a/compileopts/target.go b/compileopts/target.go
index 6c01f339e..92e9315ea 100644
--- a/compileopts/target.go
+++ b/compileopts/target.go
@@ -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",
diff --git a/compiler/defer.go b/compiler/defer.go
index 27e903319..5ec3ef7e2 100644
--- a/compiler/defer.go
+++ b/compiler/defer.go
@@ -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
diff --git a/src/internal/task/task_stack_arm64.go b/src/internal/task/task_stack_arm64.go
index 74b8bc2cb..4cf500bda 100644
--- a/src/internal/task/task_stack_arm64.go
+++ b/src/internal/task/task_stack_arm64.go
@@ -1,4 +1,4 @@
-//go:build scheduler.tasks && arm64
+//go:build scheduler.tasks && arm64 && !windows
package task
diff --git a/src/internal/task/task_stack_arm64_windows.S b/src/internal/task/task_stack_arm64_windows.S
new file mode 100644
index 000000000..45c1ec4dd
--- /dev/null
+++ b/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
diff --git a/src/internal/task/task_stack_arm64_windows.go b/src/internal/task/task_stack_arm64_windows.go
new file mode 100644
index 000000000..c3c6e8f02
--- /dev/null
+++ b/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
+}
diff --git a/src/runtime/asm_arm64_windows.S b/src/runtime/asm_arm64_windows.S
new file mode 100644
index 000000000..243778997
--- /dev/null
+++ b/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