all: add support for windows/amd64

This uses Mingw-w64, which seems to be the de facto standard for porting
Unixy programs to Windows.
This commit is contained in:
Ayke van Laethem
2021-11-05 13:23:08 +01:00
committed by Ron Evans
parent 41bcad9c19
commit 869e917dc6
26 changed files with 841 additions and 161 deletions
+42
View File
@@ -0,0 +1,42 @@
package rand
import "errors"
func init() {
Reader = &reader{}
}
type reader struct {
}
var errRandom = errors.New("failed to obtain random data from rand_s")
func (r *reader) Read(b []byte) (n int, err error) {
if len(b) == 0 {
return
}
var randomByte uint32
for i := range b {
// Call rand_s every four bytes because it's a C int (always 32-bit in
// Windows).
if i%4 == 0 {
errCode := libc_rand_s(&randomByte)
if errCode != 0 {
// According to the documentation, it can return an error.
return n, errRandom
}
} else {
randomByte >>= 8
}
b[i] = byte(randomByte)
}
return len(b), nil
}
// Cryptographically secure random number generator.
// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/rand-s?view=msvc-170
// errno_t rand_s(unsigned int* randomValue);
//export rand_s
func libc_rand_s(randomValue *uint32) int32
+1 -1
View File
@@ -1,4 +1,4 @@
// +build scheduler.tasks,amd64
// +build scheduler.tasks,amd64,!windows
package task
@@ -0,0 +1,57 @@
// Windows on amd64 has a slightly different ABI than other (*nix) systems.
// Therefore, assembly functions need to be tweaked slightly.
.section .text.tinygo_startTask,"ax"
.global tinygo_startTask
tinygo_startTask:
// Small assembly stub for starting a goroutine. This is already run on the
// new stack, with the callee-saved registers already loaded.
// Most importantly, r12 contain the pc of the to-be-started function and
// r13 contain the only argument it is given. Multiple arguments are packed
// into one by storing them in a new allocation.
// Set the first argument of the goroutine start wrapper, which contains all
// the arguments.
movq %r13, %rcx
// Branch to the "goroutine start" function.
callq *%r12
// After return, exit this goroutine. This is a tail call.
jmp tinygo_pause
.global tinygo_swapTask
.section .text.tinygo_swapTask,"ax"
tinygo_swapTask:
// This function gets the following parameters:
// %rcx = newStack uintptr
// %rdx = oldStack *uintptr
// Save all callee-saved registers:
pushq %r15
pushq %r14
pushq %r13
pushq %r12
pushq %rsi
pushq %rdi
pushq %rbp
pushq %rbx
// Save the current stack pointer in oldStack.
movq %rsp, (%rdx)
// Switch to the new stack pointer.
movq %rcx, %rsp
// Load saved register from the new stack.
popq %rbx
popq %rbp
popq %rdi
popq %rsi
popq %r12
popq %r13
popq %r14
popq %r15
// Return into the new task, as if tinygo_swapTask was a regular call.
ret
@@ -0,0 +1,66 @@
// +build scheduler.tasks,amd64,windows
package task
// This is almost the same as task_stack_amd64.go, but with the extra rdi and
// rsi registers saved: Windows has a slightly different calling convention.
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_amd64_windows.S that relies on
// the exact layout of this struct.
type calleeSavedRegs struct {
rbx uintptr
rbp uintptr
rdi uintptr
rsi uintptr
r12 uintptr
r13 uintptr
r14 uintptr
r15 uintptr
pc 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_amd64_windows.S). This assembly code calls a
// function (passed in r12) with a single argument (passed in r13). After
// the function returns, it calls Pause().
r.pc = uintptr(unsafe.Pointer(&startTask))
// Pass the function to call in r12.
// 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.r12 = fn
// Pass the pointer to the arguments struct in r13.
r.r13 = 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
}
+114
View File
@@ -0,0 +1,114 @@
// +build !baremetal,!js
package os
import (
"io"
"syscall"
)
func init() {
// Mount the host filesystem at the root directory. This is what most
// programs will be expecting.
Mount("/", unixFilesystem{})
}
// Stdin, Stdout, and Stderr are open Files pointing to the standard input,
// standard output, and standard error file descriptors.
var (
Stdin = &File{unixFileHandle(syscall.Stdin), "/dev/stdin"}
Stdout = &File{unixFileHandle(syscall.Stdout), "/dev/stdout"}
Stderr = &File{unixFileHandle(syscall.Stderr), "/dev/stderr"}
)
// isOS indicates whether we're running on a real operating system with
// filesystem support.
const isOS = true
// unixFilesystem is an empty handle for a Unix/Linux filesystem. All operations
// are relative to the current working directory.
type unixFilesystem struct {
}
func (fs unixFilesystem) Mkdir(path string, perm FileMode) error {
return handleSyscallError(syscall.Mkdir(path, uint32(perm)))
}
func (fs unixFilesystem) Remove(path string) error {
return handleSyscallError(syscall.Unlink(path))
}
func (fs unixFilesystem) OpenFile(path string, flag int, perm FileMode) (FileHandle, error) {
// Map os package flags to syscall flags.
syscallFlag := 0
if flag&O_RDONLY != 0 {
syscallFlag |= syscall.O_RDONLY
}
if flag&O_WRONLY != 0 {
syscallFlag |= syscall.O_WRONLY
}
if flag&O_RDWR != 0 {
syscallFlag |= syscall.O_RDWR
}
if flag&O_APPEND != 0 {
syscallFlag |= syscall.O_APPEND
}
if flag&O_CREATE != 0 {
syscallFlag |= syscall.O_CREAT
}
if flag&O_EXCL != 0 {
syscallFlag |= syscall.O_EXCL
}
if flag&O_SYNC != 0 {
syscallFlag |= syscall.O_SYNC
}
if flag&O_TRUNC != 0 {
syscallFlag |= syscall.O_TRUNC
}
fp, err := syscall.Open(path, syscallFlag, uint32(perm))
return unixFileHandle(fp), handleSyscallError(err)
}
// unixFileHandle is a Unix file pointer with associated methods that implement
// the FileHandle interface.
type unixFileHandle uintptr
// Read reads up to len(b) bytes from the File. It returns the number of bytes
// read and any error encountered. At end of file, Read returns 0, io.EOF.
func (f unixFileHandle) Read(b []byte) (n int, err error) {
n, err = syscall.Read(syscallFd(f), b)
err = handleSyscallError(err)
if n == 0 && err == nil {
err = io.EOF
}
return
}
// Write writes len(b) bytes to the File. It returns the number of bytes written
// and an error, if any. Write returns a non-nil error when n != len(b).
func (f unixFileHandle) Write(b []byte) (n int, err error) {
n, err = syscall.Write(syscallFd(f), b)
err = handleSyscallError(err)
return
}
// Close closes the File, rendering it unusable for I/O.
func (f unixFileHandle) Close() error {
return handleSyscallError(syscall.Close(syscallFd(f)))
}
// handleSyscallError converts syscall errors into regular os package errors.
// The err parameter must be either nil or of type syscall.Errno.
func handleSyscallError(err error) error {
if err == nil {
return nil
}
switch err.(syscall.Errno) {
case syscall.EEXIST:
return ErrExist
case syscall.ENOENT:
return ErrNotExist
default:
return err
}
}
+1 -110
View File
@@ -2,113 +2,4 @@
package os
import (
"io"
"syscall"
)
func init() {
// Mount the host filesystem at the root directory. This is what most
// programs will be expecting.
Mount("/", unixFilesystem{})
}
// Stdin, Stdout, and Stderr are open Files pointing to the standard input,
// standard output, and standard error file descriptors.
var (
Stdin = &File{unixFileHandle(0), "/dev/stdin"}
Stdout = &File{unixFileHandle(1), "/dev/stdout"}
Stderr = &File{unixFileHandle(2), "/dev/stderr"}
)
// isOS indicates whether we're running on a real operating system with
// filesystem support.
const isOS = true
// unixFilesystem is an empty handle for a Unix/Linux filesystem. All operations
// are relative to the current working directory.
type unixFilesystem struct {
}
func (fs unixFilesystem) Mkdir(path string, perm FileMode) error {
return handleSyscallError(syscall.Mkdir(path, uint32(perm)))
}
func (fs unixFilesystem) Remove(path string) error {
return handleSyscallError(syscall.Unlink(path))
}
func (fs unixFilesystem) OpenFile(path string, flag int, perm FileMode) (FileHandle, error) {
// Map os package flags to syscall flags.
syscallFlag := 0
if flag&O_RDONLY != 0 {
syscallFlag |= syscall.O_RDONLY
}
if flag&O_WRONLY != 0 {
syscallFlag |= syscall.O_WRONLY
}
if flag&O_RDWR != 0 {
syscallFlag |= syscall.O_RDWR
}
if flag&O_APPEND != 0 {
syscallFlag |= syscall.O_APPEND
}
if flag&O_CREATE != 0 {
syscallFlag |= syscall.O_CREAT
}
if flag&O_EXCL != 0 {
syscallFlag |= syscall.O_EXCL
}
if flag&O_SYNC != 0 {
syscallFlag |= syscall.O_SYNC
}
if flag&O_TRUNC != 0 {
syscallFlag |= syscall.O_TRUNC
}
fp, err := syscall.Open(path, syscallFlag, uint32(perm))
return unixFileHandle(fp), handleSyscallError(err)
}
// unixFileHandle is a Unix file pointer with associated methods that implement
// the FileHandle interface.
type unixFileHandle uintptr
// Read reads up to len(b) bytes from the File. It returns the number of bytes
// read and any error encountered. At end of file, Read returns 0, io.EOF.
func (f unixFileHandle) Read(b []byte) (n int, err error) {
n, err = syscall.Read(int(f), b)
err = handleSyscallError(err)
if n == 0 && err == nil {
err = io.EOF
}
return
}
// Write writes len(b) bytes to the File. It returns the number of bytes written
// and an error, if any. Write returns a non-nil error when n != len(b).
func (f unixFileHandle) Write(b []byte) (n int, err error) {
n, err = syscall.Write(int(f), b)
err = handleSyscallError(err)
return
}
// Close closes the File, rendering it unusable for I/O.
func (f unixFileHandle) Close() error {
return handleSyscallError(syscall.Close(int(f)))
}
// handleSyscallError converts syscall errors into regular os package errors.
// The err parameter must be either nil or of type syscall.Errno.
func handleSyscallError(err error) error {
if err == nil {
return nil
}
switch err.(syscall.Errno) {
case syscall.EEXIST:
return ErrExist
case syscall.ENOENT:
return ErrNotExist
default:
return err
}
}
type syscallFd = int
+7
View File
@@ -0,0 +1,7 @@
// +build windows
package os
import "syscall"
type syscallFd = syscall.Handle
+22
View File
@@ -0,0 +1,22 @@
.section .text.tinygo_scanCurrentStack,"ax"
.global tinygo_scanCurrentStack
tinygo_scanCurrentStack:
// Save callee-saved registers.
pushq %rbx
pushq %rbp
pushq %rdi
pushq %rsi
pushq %r12
pushq %r13
pushq %r14
pushq %r15
// Scan the stack.
subq $8, %rsp // adjust the stack before the call to maintain 16-byte alignment
movq %rsp, %rdi
callq tinygo_scanstack
// Restore the stack pointer. Registers do not need to be restored as they
// were only pushed to be discoverable by the GC.
addq $72, %rsp
retq
+3
View File
@@ -0,0 +1,3 @@
package runtime
const GOOS = "windows"
+230
View File
@@ -0,0 +1,230 @@
package runtime
import "unsafe"
//export abort
func abort()
//export exit
func libc_exit(code int)
//export putchar
func libc_putchar(c int) int
//export VirtualAlloc
func _VirtualAlloc(lpAddress unsafe.Pointer, dwSize uintptr, flAllocationType, flProtect uint32) unsafe.Pointer
//export QueryUnbiasedInterruptTime
func _QueryUnbiasedInterruptTime(UnbiasedTime *uint64) bool
// The parameter is really a LPFILETIME, but *uint64 should be compatible.
//export GetSystemTimeAsFileTime
func _GetSystemTimeAsFileTime(lpSystemTimeAsFileTime *uint64)
//export LoadLibraryExW
func _LoadLibraryExW(lpLibFileName *uint16, hFile uintptr, dwFlags uint32) uintptr
//export Sleep
func _Sleep(milliseconds uint32)
const _LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800
//export GetProcAddress
func getProcAddress(handle uintptr, procname *byte) uintptr
//export _configure_narrow_argv
func _configure_narrow_argv(int32) int32
//export __p___argc
func __p___argc() *int32
//export __p___argv
func __p___argv() **unsafe.Pointer
func postinit() {}
//export mainCRTStartup
func mainCRTStartup() int {
preinit()
// Obtain the initial stack pointer right before calling the run() function.
// The run function has been moved to a separate (non-inlined) function so
// that the correct stack pointer is read.
stackTop = getCurrentStackPointer()
runMain()
// For libc compatibility.
return 0
}
// Must be a separate function to get the correct stack pointer.
//go:noinline
func runMain() {
run()
}
var args []string
//go:linkname os_runtime_args os.runtime_args
func os_runtime_args() []string {
if args == nil {
// Obtain argc/argv from the environment.
_configure_narrow_argv(2)
argc := *__p___argc()
argv := *__p___argv()
// Make args slice big enough so that it can store all command line
// arguments.
args = make([]string, argc)
// Initialize command line parameters.
for i := 0; i < int(argc); i++ {
// Convert the C string to a Go string.
length := strlen(*argv)
arg := (*_string)(unsafe.Pointer(&args[i]))
arg.length = length
arg.ptr = (*byte)(*argv)
// This is the Go equivalent of "argv++" in C.
argv = (*unsafe.Pointer)(unsafe.Pointer(uintptr(unsafe.Pointer(argv)) + unsafe.Sizeof(argv)))
}
}
return args
}
func putchar(c byte) {
libc_putchar(int(c))
}
var heapSize uintptr = 128 * 1024 // small amount to start
var heapMaxSize uintptr
var heapStart, heapEnd uintptr
func preinit() {
// Allocate a large chunk of virtual memory. Because it is virtual, it won't
// really be allocated in RAM. Memory will only be allocated when it is
// first touched.
heapMaxSize = 1 * 1024 * 1024 * 1024 // 1GB for the entire heap
const (
MEM_COMMIT = 0x00001000
MEM_RESERVE = 0x00002000
PAGE_READWRITE = 0x04
)
heapStart = uintptr(_VirtualAlloc(nil, heapMaxSize, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE))
heapEnd = heapStart + heapSize
}
type timeUnit int64
var stackTop uintptr
func ticksToNanoseconds(ticks timeUnit) int64 {
// Interrupt time count works in units of 100 nanoseconds.
return int64(ticks) * 100
}
func nanosecondsToTicks(ns int64) timeUnit {
// Interrupt time count works in units of 100 nanoseconds.
return timeUnit(ns) / 100
}
func sleepTicks(d timeUnit) {
// Calcluate milliseconds from ticks (which have a resolution of 100ns),
// rounding up.
milliseconds := int64(d+9_999) / 10_000
for milliseconds != 0 {
duration := uint32(milliseconds)
_Sleep(duration)
milliseconds -= int64(duration)
}
}
func ticks() timeUnit {
var unbiasedTime uint64
_QueryUnbiasedInterruptTime(&unbiasedTime)
return timeUnit(unbiasedTime)
}
//go:linkname now time.now
func now() (sec int64, nsec int32, mono int64) {
// Get the current time in Windows "file time" format.
var time uint64
_GetSystemTimeAsFileTime(&time)
// Convert file time to Unix time.
// According to the documentation:
// > Contains a 64-bit value representing the number of 100-nanosecond
// > intervals since January 1, 1601 (UTC).
// We'll convert it to 100 nanosecond intervals starting at 1970.
const (
// number of 100-nanosecond intervals in a second
intervalsPerSecond = 10_000_000
secondsPerDay = 60 * 60 * 24
// Number of days between the Windows epoch (1 january 1601) and the
// Unix epoch (1 january 1970). Source:
// https://www.wolframalpha.com/input/?i=days+between+1+january+1601+and+1+january+1970
days = 134774
)
time -= days * secondsPerDay * intervalsPerSecond
// Convert the time (in 100ns units) to sec/nsec/mono as expected by the
// time package.
sec = int64(time / intervalsPerSecond)
nsec = int32((time - (uint64(sec) * intervalsPerSecond)) * 100)
mono = ticksToNanoseconds(ticks())
return
}
//go:linkname syscall_Exit syscall.Exit
func syscall_Exit(code int) {
libc_exit(code)
}
func growHeap() bool {
if heapSize == heapMaxSize {
// Already at the max. If we run out of memory, we should consider
// increasing heapMaxSize..
return false
}
// Grow the heap size used by the program.
heapSize = (heapSize * 4 / 3) &^ 4095 // grow by around 33%
if heapSize > heapMaxSize {
heapSize = heapMaxSize
}
setHeapEnd(heapStart + heapSize)
return true
}
//go:linkname syscall_loadsystemlibrary syscall.loadsystemlibrary
func syscall_loadsystemlibrary(filename *uint16, absoluteFilepath *uint16) (handle, err uintptr) {
handle = _LoadLibraryExW(filename, 0, _LOAD_LIBRARY_SEARCH_SYSTEM32)
if handle == 0 {
panic("todo: get error")
}
return
}
//go:linkname syscall_loadlibrary syscall.loadlibrary
func syscall_loadlibrary(filename *uint16) (handle, err uintptr) {
panic("todo: syscall.loadlibrary")
}
//go:linkname syscall_getprocaddress syscall.getprocaddress
func syscall_getprocaddress(handle uintptr, procname *byte) (outhandle, err uintptr) {
outhandle = getProcAddress(handle, procname)
if outhandle == 0 {
panic("todo: get error")
}
return
}
// TinyGo does not yet support any form of parallelism on Windows, so these can
// be left empty.
//go:linkname procPin sync/atomic.runtime_procPin
func procPin() {
}
//go:linkname procUnpin sync/atomic.runtime_procUnpin
func procUnpin() {
}
+6
View File
@@ -62,6 +62,12 @@ const (
SIGTERM Signal = 0xf
)
const (
Stdin = 0
Stdout = 1
Stderr = 2
)
const (
O_RDONLY = 0x0
O_WRONLY = 0x1