Browse Source

all: add support for windows/amd64

This uses Mingw-w64, which seems to be the de facto standard for porting
Unixy programs to Windows.
pull/2272/head
Ayke van Laethem 3 years ago
committed by Ron Evans
parent
commit
869e917dc6
  1. 2
      .github/workflows/windows.yml
  2. 3
      .gitmodules
  3. 12
      Makefile
  4. 70
      builder/ar.go
  5. 9
      builder/build.go
  6. 1
      builder/builder_test.go
  7. 5
      builder/lld.cpp
  8. 91
      builder/mingw-w64.go
  9. 14
      builder/tools-builtin.go
  10. 9
      compileopts/config.go
  11. 34
      compileopts/target.go
  12. 63
      compiler/syscall.go
  13. 1
      lib/mingw-w64
  14. 25
      main_test.go
  15. 42
      src/crypto/rand/rand_windows.go
  16. 2
      src/internal/task/task_stack_amd64.go
  17. 57
      src/internal/task/task_stack_amd64_windows.S
  18. 66
      src/internal/task/task_stack_amd64_windows.go
  19. 114
      src/os/file_anyos.go
  20. 111
      src/os/file_unix.go
  21. 7
      src/os/file_windows.go
  22. 22
      src/runtime/gc_amd64_windows.S
  23. 3
      src/runtime/os_windows.go
  24. 230
      src/runtime/runtime_windows.go
  25. 6
      src/syscall/syscall_libc_darwin.go
  26. 3
      testdata/filesystem.go

2
.github/workflows/windows.yml

@ -99,3 +99,5 @@ jobs:
- name: Smoke tests
shell: bash
run: make smoketest TINYGO=build/tinygo AVR=0 XTENSA=0
- name: Test stdlib packages
run: make tinygo-test

3
.gitmodules

@ -29,3 +29,6 @@
[submodule "lib/binaryen"]
path = lib/binaryen
url = https://github.com/WebAssembly/binaryen.git
[submodule "lib/mingw-w64"]
path = lib/mingw-w64
url = https://github.com/mingw-w64/mingw-w64.git

12
Makefile

@ -38,7 +38,7 @@ endif
.PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-nxp gen-device-avr gen-device-rp
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf executionengine frontendopenmp instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf debuginfopdb executionengine frontendopenmp instrumentation interpreter ipo irreader libdriver linker lto mc mcjit objcarcopts option profiledata scalaropts support target windowsmanifest
ifeq ($(OS),Windows_NT)
EXE = .exe
@ -477,7 +477,10 @@ endif
$(TINYGO) build -size short -o test.hex -target=pca10040 -opt=0 ./testdata/stdlib.go
@$(MD5SUM) test.hex
GOOS=linux GOARCH=arm $(TINYGO) build -size short -o test.elf ./testdata/cgo
GOOS=windows GOARCH=amd64 $(TINYGO) build -o test.exe ./testdata/cgo
ifneq ($(OS),Windows_NT)
# TODO: this does not yet work on Windows. Somehow, unused functions are
# not garbage collected.
$(TINYGO) build -o test.elf -gc=leaking -scheduler=none examples/serial
endif
@ -490,6 +493,8 @@ build/release: tinygo gen-device wasi-libc binaryen
@mkdir -p build/release/tinygo/lib/clang/include
@mkdir -p build/release/tinygo/lib/CMSIS/CMSIS
@mkdir -p build/release/tinygo/lib/compiler-rt/lib
@mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
@mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-headers/defaults
@mkdir -p build/release/tinygo/lib/musl/arch
@mkdir -p build/release/tinygo/lib/musl/crt
@mkdir -p build/release/tinygo/lib/musl/src
@ -530,6 +535,11 @@ build/release: tinygo gen-device wasi-libc binaryen
@cp -rp lib/musl/src/thread build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/time build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/unistd build/release/tinygo/lib/musl/src
@cp -rp lib/mingw-w64/mingw-w64-crt/def-include build/release/tinygo/lib/mingw-w64/mingw-w64-crt
@cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/api-ms-win-crt-* build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
@cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/kernel32.def.in build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
@cp -rp lib/mingw-w64/mingw-w64-headers/crt/ build/release/tinygo/lib/mingw-w64/mingw-w64-headers
@cp -rp lib/mingw-w64/mingw-w64-headers/defaults/include build/release/tinygo/lib/mingw-w64/mingw-w64-headers/defaults
@cp -rp lib/nrfx/* build/release/tinygo/lib/nrfx
@cp -rp lib/picolibc/newlib/libc/ctype build/release/tinygo/lib/picolibc/newlib/libc
@cp -rp lib/picolibc/newlib/libc/include build/release/tinygo/lib/picolibc/newlib/libc

70
builder/ar.go

@ -3,6 +3,7 @@ package builder
import (
"bytes"
"debug/elf"
"debug/pe"
"encoding/binary"
"errors"
"fmt"
@ -39,29 +40,42 @@ func makeArchive(arfile *os.File, objs []string) error {
}
// Read the symbols and add them to the symbol table.
dbg, err := elf.NewFile(objfile)
if err != nil {
return fmt.Errorf("failed to open file %s: %w", objpath, err)
}
symbols, err := dbg.Symbols()
if err != nil {
return err
}
for _, symbol := range symbols {
bind := elf.ST_BIND(symbol.Info)
if bind != elf.STB_GLOBAL && bind != elf.STB_WEAK {
// Don't include local symbols (STB_LOCAL).
continue
if dbg, err := elf.NewFile(objfile); err == nil {
symbols, err := dbg.Symbols()
if err != nil {
return err
}
if elf.ST_TYPE(symbol.Info) != elf.STT_FUNC && elf.ST_TYPE(symbol.Info) != elf.STT_OBJECT {
// Not a function.
continue
for _, symbol := range symbols {
bind := elf.ST_BIND(symbol.Info)
if bind != elf.STB_GLOBAL && bind != elf.STB_WEAK {
// Don't include local symbols (STB_LOCAL).
continue
}
if elf.ST_TYPE(symbol.Info) != elf.STT_FUNC && elf.ST_TYPE(symbol.Info) != elf.STT_OBJECT {
// Not a function.
continue
}
// Include in archive.
symbolTable = append(symbolTable, struct {
name string
fileIndex int
}{symbol.Name, i})
}
// Include in archive.
symbolTable = append(symbolTable, struct {
name string
fileIndex int
}{symbol.Name, i})
} else if dbg, err := pe.NewFile(objfile); err == nil {
for _, symbol := range dbg.Symbols {
if symbol.StorageClass != 2 {
continue
}
if symbol.SectionNumber == 0 {
continue
}
symbolTable = append(symbolTable, struct {
name string
fileIndex int
}{symbol.Name, i})
}
} else {
return fmt.Errorf("failed to open file %s as ELF or PE/COFF: %w", objpath, err)
}
// Close file, to avoid issues with too many open files (especially on
@ -120,11 +134,13 @@ func makeArchive(arfile *os.File, objs []string) error {
}
// Add all object files to the archive.
var copyBuf bytes.Buffer
for i, objpath := range objs {
objfile, err := os.Open(objpath)
if err != nil {
return err
}
defer objfile.Close()
// Store the start index, for when we'll update the symbol table with
// the correct file start indices.
@ -155,13 +171,21 @@ func makeArchive(arfile *os.File, objs []string) error {
}
// Copy the file contents into the archive.
n, err := io.Copy(arwriter, objfile)
// First load all contents into a buffer, then write it all in one go to
// the archive file. This is a bit complicated, but is necessary because
// io.Copy can't deal with files that are of an odd size.
copyBuf.Reset()
n, err := io.Copy(&copyBuf, objfile)
if err != nil {
return err
return fmt.Errorf("could not copy object file into ar file: %w", err)
}
if n != st.Size() {
return errors.New("file modified during ar creation: " + arfile.Name())
}
_, err = arwriter.Write(copyBuf.Bytes())
if err != nil {
return fmt.Errorf("could not copy object file into ar file: %w", err)
}
// File is not needed anymore.
objfile.Close()

9
builder/build.go

@ -115,6 +115,12 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return errors.New("could not find wasi-libc, perhaps you need to run `make wasi-libc`?")
}
libcDependencies = append(libcDependencies, dummyCompileJob(path))
case "mingw-w64":
_, err := MinGW.load(config, dir)
if err != nil {
return err
}
libcDependencies = append(libcDependencies, makeMinGWExtraLibs(dir)...)
case "":
// no library specified, so nothing to do
default:
@ -518,6 +524,9 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
// Prepare link command.
linkerDependencies := []*compileJob{outputObjectFileJob}
executable := filepath.Join(dir, "main")
if config.GOOS() == "windows" {
executable += ".exe"
}
tmppath := executable // final file
ldflags := append(config.LDFlags(), "-o", executable)

1
builder/builder_test.go

@ -58,6 +58,7 @@ func TestClangAttributes(t *testing.T) {
{GOOS: "linux", GOARCH: "arm64"},
{GOOS: "darwin", GOARCH: "amd64"},
{GOOS: "darwin", GOARCH: "arm64"},
{GOOS: "windows", GOARCH: "amd64"},
} {
t.Run("GOOS="+options.GOOS+",GOARCH="+options.GOARCH, func(t *testing.T) {
testClangAttributes(t, options)

5
builder/lld.cpp

@ -11,6 +11,11 @@ bool tinygo_link_elf(int argc, char **argv) {
return lld::elf::link(args, false, llvm::outs(), llvm::errs());
}
bool tinygo_link_mingw(int argc, char **argv) {
std::vector<const char*> args(argv, argv + argc);
return lld::mingw::link(args, false, llvm::outs(), llvm::errs());
}
bool tinygo_link_wasm(int argc, char **argv) {
std::vector<const char*> args(argv, argv + argc);
return lld::wasm::link(args, false, llvm::outs(), llvm::errs());

91
builder/mingw-w64.go

@ -0,0 +1,91 @@
package builder
import (
"io"
"os"
"path/filepath"
"strings"
"github.com/tinygo-org/tinygo/goenv"
)
var MinGW = Library{
name: "mingw-w64",
makeHeaders: func(target, includeDir string) error {
// copy _mingw.h
srcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib", "mingw-w64")
outf, err := os.Create(includeDir + "/_mingw.h")
if err != nil {
return err
}
defer outf.Close()
inf, err := os.Open(srcDir + "/mingw-w64-headers/crt/_mingw.h.in")
if err != nil {
return err
}
_, err = io.Copy(outf, inf)
return err
},
cflags: func(target, headerPath string) []string {
// No flags necessary because there are no files to compile.
return nil
},
librarySources: func(target string) []string {
// We only use the UCRT DLL file. No source files necessary.
return nil
},
}
// makeMinGWExtraLibs returns a slice of jobs to import the correct .dll
// libraries. This is done by converting input .def files to .lib files which
// can then be linked as usual.
//
// TODO: cache the result. At the moment, it costs a few hundred milliseconds to
// compile these files.
func makeMinGWExtraLibs(tmpdir string) []*compileJob {
var jobs []*compileJob
root := goenv.Get("TINYGOROOT")
// Normally all the api-ms-win-crt-*.def files are all compiled to a single
// .lib file. But to simplify things, we're going to leave them as separate
// files.
for _, name := range []string{
"kernel32.def.in",
"api-ms-win-crt-conio-l1-1-0.def",
"api-ms-win-crt-convert-l1-1-0.def",
"api-ms-win-crt-environment-l1-1-0.def",
"api-ms-win-crt-filesystem-l1-1-0.def",
"api-ms-win-crt-heap-l1-1-0.def",
"api-ms-win-crt-locale-l1-1-0.def",
"api-ms-win-crt-math-l1-1-0.def.in",
"api-ms-win-crt-multibyte-l1-1-0.def",
"api-ms-win-crt-private-l1-1-0.def.in",
"api-ms-win-crt-process-l1-1-0.def",
"api-ms-win-crt-runtime-l1-1-0.def.in",
"api-ms-win-crt-stdio-l1-1-0.def",
"api-ms-win-crt-string-l1-1-0.def",
"api-ms-win-crt-time-l1-1-0.def",
"api-ms-win-crt-utility-l1-1-0.def",
} {
outpath := filepath.Join(tmpdir, filepath.Base(name)+".lib")
inpath := filepath.Join(root, "lib/mingw-w64/mingw-w64-crt/lib-common/"+name)
job := &compileJob{
description: "create lib file " + inpath,
result: outpath,
run: func(job *compileJob) error {
defpath := inpath
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", "-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)
},
}
jobs = append(jobs, job)
}
return jobs
}

14
builder/tools-builtin.go

@ -13,6 +13,7 @@ import (
#include <stdlib.h>
bool tinygo_clang_driver(int argc, char **argv);
bool tinygo_link_elf(int argc, char **argv);
bool tinygo_link_mingw(int argc, char **argv);
bool tinygo_link_wasm(int argc, char **argv);
*/
import "C"
@ -24,6 +25,10 @@ const hasBuiltinTools = true
// This version actually runs the tools because TinyGo was compiled while
// linking statically with LLVM (with the byollvm build tag).
func RunTool(tool string, args ...string) error {
linker := "elf"
if tool == "ld.lld" && len(args) >= 2 && args[0] == "-m" && args[1] == "i386pep" {
linker = "mingw"
}
args = append([]string{"tinygo:" + tool}, args...)
var cflag *C.char
@ -41,7 +46,14 @@ func RunTool(tool string, args ...string) error {
case "clang":
ok = C.tinygo_clang_driver(C.int(len(args)), (**C.char)(buf))
case "ld.lld":
ok = C.tinygo_link_elf(C.int(len(args)), (**C.char)(buf))
switch linker {
case "elf":
ok = C.tinygo_link_elf(C.int(len(args)), (**C.char)(buf))
case "mingw":
ok = C.tinygo_link_mingw(C.int(len(args)), (**C.char)(buf))
default:
return errors.New("unknown linker: " + linker)
}
case "wasm-ld":
ok = C.tinygo_link_wasm(C.int(len(args)), (**C.char)(buf))
default:

9
compileopts/config.go

@ -272,6 +272,15 @@ func (c *Config) CFlags() []string {
case "wasi-libc":
root := goenv.Get("TINYGOROOT")
cflags = append(cflags, "--sysroot="+root+"/lib/wasi-libc/sysroot")
case "mingw-w64":
root := goenv.Get("TINYGOROOT")
path, _ := c.LibcPath("mingw-w64")
cflags = append(cflags,
"--sysroot="+path,
"-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "crt"),
"-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "defaults", "include"),
"-D_UCRT",
)
case "":
// No libc specified, nothing to add.
default:

34
compileopts/target.go

@ -206,6 +206,9 @@ func LoadTarget(options *Options) (*TargetSpec, error) {
if options.GOARCH == "arm" {
target += "-gnueabihf"
}
if options.GOOS == "windows" {
target += "-gnu"
}
return defaultTarget(options.GOOS, options.GOARCH, target)
}
@ -230,13 +233,7 @@ func LoadTarget(options *Options) (*TargetSpec, error) {
return spec, nil
}
// WindowsBuildNotSupportedErr is being thrown, when goos is windows and no target has been specified.
var WindowsBuildNotSupportedErr = errors.New("Building Windows binaries is currently not supported. Try specifying a different target")
func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
if goos == "windows" {
return nil, WindowsBuildNotSupportedErr
}
// No target spec available. Use the default one, useful on most systems
// with a regular OS.
spec := TargetSpec{
@ -279,12 +276,28 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
spec.RTLib = "compiler-rt"
spec.Libc = "musl"
spec.LDFlags = append(spec.LDFlags, "--gc-sections")
} else if goos == "windows" {
spec.Linker = "ld.lld"
spec.Libc = "mingw-w64"
spec.LDFlags = append(spec.LDFlags,
"-m", "i386pep",
"-Bdynamic",
"--image-base", "0x400000",
"--gc-sections",
"--no-insert-timestamp",
)
} else {
spec.LDFlags = append(spec.LDFlags, "-no-pie", "-Wl,--gc-sections") // WARNING: clang < 5.0 requires -nopie
}
if goarch != "wasm" {
spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/gc_"+goarch+".S")
spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_stack_"+goarch+".S")
suffix := ""
if goos == "windows" {
// Windows uses a different calling convention from other operating
// systems so we need separate assembly files.
suffix = "_windows"
}
spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/gc_"+goarch+suffix+".S")
spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_stack_"+goarch+suffix+".S")
}
if goarch != runtime.GOARCH {
// Some educated guesses as to how to invoke helper programs.
@ -296,6 +309,11 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
spec.Emulator = []string{"qemu-aarch64"}
}
}
if goos != runtime.GOOS {
if goos == "windows" {
spec.Emulator = []string{"wine"}
}
}
return &spec, nil
}

63
compiler/syscall.go

@ -156,12 +156,12 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
// createSyscall emits instructions for the syscall.Syscall* family of
// functions, depending on the target OS/arch.
func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
syscallResult, err := b.createRawSyscall(call)
if err != nil {
return syscallResult, err
}
switch b.GOOS {
case "linux", "freebsd":
syscallResult, err := b.createRawSyscall(call)
if err != nil {
return syscallResult, err
}
// Return values: r0, r1 uintptr, err Errno
// Pseudocode:
// var err uintptr
@ -180,6 +180,10 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
retval = b.CreateInsertValue(retval, errResult, 2, "")
return retval, nil
case "darwin":
syscallResult, err := b.createRawSyscall(call)
if err != nil {
return syscallResult, err
}
// Return values: r0, r1 uintptr, err Errno
// Pseudocode:
// var err uintptr
@ -195,6 +199,57 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
retval = b.CreateInsertValue(retval, zero, 1, "")
retval = b.CreateInsertValue(retval, errResult, 2, "")
return retval, nil
case "windows":
// On Windows, syscall.Syscall* is basically just a function pointer
// call. This is complicated in gc because of stack switching and the
// different ABI, but easy in TinyGo: just call the function pointer.
// The signature looks like this:
// func Syscall(trap, nargs, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)
// Prepare input values.
var paramTypes []llvm.Type
var params []llvm.Value
for _, val := range call.Args[2:] {
param := b.getValue(val)
params = append(params, param)
paramTypes = append(paramTypes, param.Type())
}
llvmType := llvm.FunctionType(b.uintptrType, paramTypes, false)
fn := b.getValue(call.Args[0])
fnPtr := b.CreateIntToPtr(fn, llvm.PointerType(llvmType, 0), "")
// Prepare some functions that will be called later.
setLastError := b.mod.NamedFunction("SetLastError")
if setLastError.IsNil() {
llvmType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.ctx.Int32Type()}, false)
setLastError = llvm.AddFunction(b.mod, "SetLastError", llvmType)
}
getLastError := b.mod.NamedFunction("GetLastError")
if getLastError.IsNil() {
llvmType := llvm.FunctionType(b.ctx.Int32Type(), nil, false)
getLastError = llvm.AddFunction(b.mod, "GetLastError", llvmType)
}
// Now do the actual call. Pseudocode:
// SetLastError(0)
// r1 = trap(a1, a2, a3, ...)
// err = uintptr(GetLastError())
// return r1, 0, err
// Note that SetLastError/GetLastError could be replaced with direct
// access to the thread control block, which is probably smaller and
// faster. The Go runtime does this in assembly.
b.CreateCall(setLastError, []llvm.Value{llvm.ConstNull(b.ctx.Int32Type())}, "")
syscallResult := b.CreateCall(fnPtr, params, "")
errResult := b.CreateCall(getLastError, nil, "err")
if b.uintptrType != b.ctx.Int32Type() {
errResult = b.CreateZExt(errResult, b.uintptrType, "err.uintptr")
}
// Return r1, 0, err
retval := llvm.ConstNull(b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType, b.uintptrType}, false))
retval = b.CreateInsertValue(retval, syscallResult, 0, "")
retval = b.CreateInsertValue(retval, errResult, 2, "")
return retval, nil
default:
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH)
}

1
lib/mingw-w64

@ -0,0 +1 @@
Subproject commit acc9b9d9eb63a13d8122cbac4882eb5f4ee2f679

25
main_test.go

@ -71,11 +71,9 @@ func TestCompiler(t *testing.T) {
return
}
if runtime.GOOS != "windows" {
t.Run("Host", func(t *testing.T) {
runPlatTests(optionsFromTarget(""), tests, t)
})
}
t.Run("Host", func(t *testing.T) {
runPlatTests(optionsFromTarget(""), tests, t)
})
if testing.Short() {
return
@ -113,10 +111,6 @@ func TestCompiler(t *testing.T) {
// Test a few build options.
t.Run("build-options", func(t *testing.T) {
if runtime.GOOS == "windows" {
// These tests assume a host that is supported by TinyGo.
t.Skip("can't test build options on Windows")
}
t.Parallel()
// Test with few optimizations enabled (no inlining, etc).
@ -298,6 +292,9 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
// Build the test binary.
binary := filepath.Join(tmpdir, "test")
if spec.GOOS == "windows" {
binary += ".exe"
}
err = runBuild("./"+path, binary, &options)
if err != nil {
printCompilerError(t.Log, err)
@ -339,7 +336,13 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
}
go func() {
// Terminate the process if it runs too long.
timer := time.NewTimer(10 * time.Second)
maxDuration := 10 * time.Second
if runtime.GOOS == "windows" {
// For some reason, tests on Windows can take around
// 30s to complete. TODO: investigate why and fix this.
maxDuration = 40 * time.Second
}
timer := time.NewTimer(maxDuration)
select {
case <-runComplete:
timer.Stop()
@ -369,7 +372,7 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
t.Log("failed to run:", err)
fail = true
} else if !bytes.Equal(expected, actual) {
t.Log("output did not match")
t.Logf("output did not match (expected %d bytes, got %d bytes):", len(expected), len(actual))
fail = true
}

42
src/crypto/rand/rand_windows.go

@ -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

2
src/internal/task/task_stack_amd64.go

@ -1,4 +1,4 @@
// +build scheduler.tasks,amd64
// +build scheduler.tasks,amd64,!windows
package task

57
src/internal/task/task_stack_amd64_windows.S

@ -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

66
src/internal/task/task_stack_amd64_windows.go

@ -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
src/os/file_anyos.go

@ -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
}
}

111
src/os/file_unix.go

@ -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
src/os/file_windows.go

@ -0,0 +1,7 @@
// +build windows
package os
import "syscall"
type syscallFd = syscall.Handle

22
src/runtime/gc_amd64_windows.S

@ -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
src/runtime/os_windows.go

@ -0,0 +1,3 @@
package runtime
const GOOS = "windows"

230
src/runtime/runtime_windows.go

@ -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
src/syscall/syscall_libc_darwin.go

@ -62,6 +62,12 @@ const (
SIGTERM Signal = 0xf
)
const (
Stdin = 0
Stdout = 1
Stderr = 2
)
const (
O_RDONLY = 0x0
O_WRONLY = 0x1

3
testdata/filesystem.go

@ -33,6 +33,5 @@ func main() {
panic(err)
}
print(string(data))
os.Stdout.Write(data)
}

Loading…
Cancel
Save