windows: use MSVCRT.DLL instead of UCRT on i386

This allows the binaries to run on Windows XP, without needing any extra
DLLs. Tested in an x86 Windows XP SP3 virtual machine.
This commit is contained in:
Ayke van Laethem
2025-04-07 15:09:32 +02:00
committed by Ron Evans
parent f542717992
commit aa63f26f36
8 changed files with 196 additions and 75 deletions
+10 -2
View File
@@ -940,8 +940,9 @@ build/release: tinygo gen-device $(if $(filter 1,$(USE_SYSTEM_BINARYEN)),,binary
@mkdir -p build/release/tinygo/lib/clang/include @mkdir -p build/release/tinygo/lib/clang/include
@mkdir -p build/release/tinygo/lib/CMSIS/CMSIS @mkdir -p build/release/tinygo/lib/CMSIS/CMSIS
@mkdir -p build/release/tinygo/lib/macos-minimal-sdk @mkdir -p build/release/tinygo/lib/macos-minimal-sdk
@mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/crt
@mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/math
@mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common @mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
@mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/stdio
@mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-headers/defaults @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/arch
@mkdir -p build/release/tinygo/lib/musl/crt @mkdir -p build/release/tinygo/lib/musl/crt
@@ -997,10 +998,17 @@ endif
@cp -rp lib/musl/src/time 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/musl/src/unistd build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/process build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/process build/release/tinygo/lib/musl/src
@cp -rp lib/mingw-w64/mingw-w64-crt/crt/pseudo-reloc.c build/release/tinygo/lib/mingw-w64/mingw-w64-crt/crt
@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/def-include build/release/tinygo/lib/mingw-w64/mingw-w64-crt
@cp -rp lib/mingw-w64/mingw-w64-crt/gdtoa build/release/tinygo/lib/mingw-w64/mingw-w64-crt
@cp -rp lib/mingw-w64/mingw-w64-crt/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/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/advapi32.def.in 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-crt/lib-common/kernel32.def.in build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
@cp -rp lib/mingw-w64/mingw-w64-crt/stdio/ucrt_* build/release/tinygo/lib/mingw-w64/mingw-w64-crt/stdio @cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/msvcrt.def.in build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
@cp -rp lib/mingw-w64/mingw-w64-crt/math/x86 build/release/tinygo/lib/mingw-w64/mingw-w64-crt/math
@cp -rp lib/mingw-w64/mingw-w64-crt/misc build/release/tinygo/lib/mingw-w64/mingw-w64-crt
@cp -rp lib/mingw-w64/mingw-w64-crt/stdio build/release/tinygo/lib/mingw-w64/mingw-w64-crt
@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/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/mingw-w64/mingw-w64-headers/defaults/include build/release/tinygo/lib/mingw-w64/mingw-w64-headers/defaults
@cp -rp lib/mingw-w64/mingw-w64-headers/include build/release/tinygo/lib/mingw-w64/mingw-w64-headers @cp -rp lib/mingw-w64/mingw-w64-headers/include build/release/tinygo/lib/mingw-w64/mingw-w64-headers
+10
View File
@@ -3,6 +3,7 @@ package builder
import ( import (
"os" "os"
"path/filepath" "path/filepath"
"strings"
"github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/goenv"
@@ -201,6 +202,11 @@ var avrBuiltins = []string{
"avr/udivmodqi4.S", "avr/udivmodqi4.S",
} }
// Builtins needed specifically for windows/386.
var windowsI386Builtins = []string{
"i386/chkstk.S", // also _alloca
}
// libCompilerRT is a library with symbols required by programs compiled with // libCompilerRT is a library with symbols required by programs compiled with
// LLVM. These symbols are for operations that cannot be emitted with a single // LLVM. These symbols are for operations that cannot be emitted with a single
// instruction or a short sequence of instructions for that target. // instruction or a short sequence of instructions for that target.
@@ -229,6 +235,10 @@ var libCompilerRT = Library{
builtins = append(builtins, avrBuiltins...) builtins = append(builtins, avrBuiltins...)
case "x86_64", "aarch64", "riscv64": // any 64-bit arch case "x86_64", "aarch64", "riscv64": // any 64-bit arch
builtins = append(builtins, genericBuiltins128...) builtins = append(builtins, genericBuiltins128...)
case "i386":
if strings.Split(target, "-")[2] == "windows" {
builtins = append(builtins, windowsI386Builtins...)
}
} }
return builtins, nil return builtins, nil
}, },
+83 -32
View File
@@ -30,25 +30,62 @@ var libMinGW = Library{
sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/mingw-w64") }, sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/mingw-w64") },
cflags: func(target, headerPath string) []string { cflags: func(target, headerPath string) []string {
mingwDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/mingw-w64") mingwDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/mingw-w64")
return []string{ flags := []string{
"-nostdlibinc", "-nostdlibinc",
"-isystem", mingwDir + "/mingw-w64-crt/include",
"-isystem", mingwDir + "/mingw-w64-headers/crt", "-isystem", mingwDir + "/mingw-w64-headers/crt",
"-isystem", mingwDir + "/mingw-w64-headers/include",
"-I", mingwDir + "/mingw-w64-headers/defaults/include", "-I", mingwDir + "/mingw-w64-headers/defaults/include",
"-I" + headerPath, "-I" + headerPath,
} }
if strings.Split(target, "-")[0] == "i386" {
flags = append(flags,
"-D__MSVCRT_VERSION__=0x700", // Microsoft Visual C++ .NET 2002
"-D_WIN32_WINNT=0x0501", // target Windows XP
"-D_CRTBLD",
"-Wno-pragma-pack",
)
}
return flags
}, },
librarySources: func(target string) ([]string, error) { librarySources: func(target string) ([]string, error) {
// These files are needed so that printf and the like are supported. // These files are needed so that printf and the like are supported.
sources := []string{ var sources []string
"mingw-w64-crt/stdio/ucrt_fprintf.c", if strings.Split(target, "-")[0] == "i386" {
"mingw-w64-crt/stdio/ucrt_fwprintf.c", // Old 32-bit x86 systems use msvcrt.dll.
"mingw-w64-crt/stdio/ucrt_printf.c", sources = []string{
"mingw-w64-crt/stdio/ucrt_snprintf.c", "mingw-w64-crt/crt/pseudo-reloc.c",
"mingw-w64-crt/stdio/ucrt_sprintf.c", "mingw-w64-crt/gdtoa/dmisc.c",
"mingw-w64-crt/stdio/ucrt_vfprintf.c", "mingw-w64-crt/gdtoa/gdtoa.c",
"mingw-w64-crt/stdio/ucrt_vprintf.c", "mingw-w64-crt/gdtoa/gmisc.c",
"mingw-w64-crt/stdio/ucrt_vsnprintf.c", "mingw-w64-crt/gdtoa/misc.c",
"mingw-w64-crt/stdio/ucrt_vsprintf.c", "mingw-w64-crt/math/x86/exp2.S",
"mingw-w64-crt/math/x86/trunc.S",
"mingw-w64-crt/misc/___mb_cur_max_func.c",
"mingw-w64-crt/misc/lc_locale_func.c",
"mingw-w64-crt/misc/mbrtowc.c",
"mingw-w64-crt/misc/strnlen.c",
"mingw-w64-crt/misc/wcrtomb.c",
"mingw-w64-crt/misc/wcsnlen.c",
"mingw-w64-crt/stdio/acrt_iob_func.c",
"mingw-w64-crt/stdio/mingw_lock.c",
"mingw-w64-crt/stdio/mingw_pformat.c",
"mingw-w64-crt/stdio/mingw_vfprintf.c",
"mingw-w64-crt/stdio/mingw_vsnprintf.c",
}
} else {
// Anything somewhat modern (amd64, arm64) uses UCRT.
sources = []string{
"mingw-w64-crt/stdio/ucrt_fprintf.c",
"mingw-w64-crt/stdio/ucrt_fwprintf.c",
"mingw-w64-crt/stdio/ucrt_printf.c",
"mingw-w64-crt/stdio/ucrt_snprintf.c",
"mingw-w64-crt/stdio/ucrt_sprintf.c",
"mingw-w64-crt/stdio/ucrt_vfprintf.c",
"mingw-w64-crt/stdio/ucrt_vprintf.c",
"mingw-w64-crt/stdio/ucrt_vsnprintf.c",
"mingw-w64-crt/stdio/ucrt_vsprintf.c",
}
} }
return sources, nil return sources, nil
}, },
@@ -63,27 +100,41 @@ var libMinGW = Library{
func makeMinGWExtraLibs(tmpdir, goarch 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 var libs []string
// .lib file. But to simplify things, we're going to leave them as separate if goarch == "386" {
// files. libs = []string{
for _, name := range []string{ // x86 uses msvcrt.dll instead of UCRT for compatibility with old
"kernel32.def.in", // Windows versions.
"api-ms-win-crt-conio-l1-1-0.def", "advapi32.def.in",
"api-ms-win-crt-convert-l1-1-0.def.in", "kernel32.def.in",
"api-ms-win-crt-environment-l1-1-0.def", "msvcrt.def.in",
"api-ms-win-crt-filesystem-l1-1-0.def", }
"api-ms-win-crt-heap-l1-1-0.def", } else {
"api-ms-win-crt-locale-l1-1-0.def", // Use the modernized UCRT on new systems.
"api-ms-win-crt-math-l1-1-0.def.in", // Normally all the api-ms-win-crt-*.def files are all compiled to a
"api-ms-win-crt-multibyte-l1-1-0.def", // single .lib file. But to simplify things, we're going to leave them
"api-ms-win-crt-private-l1-1-0.def.in", // as separate files.
"api-ms-win-crt-process-l1-1-0.def", libs = []string{
"api-ms-win-crt-runtime-l1-1-0.def.in", "advapi32.def.in",
"api-ms-win-crt-stdio-l1-1-0.def", "kernel32.def.in",
"api-ms-win-crt-string-l1-1-0.def", "api-ms-win-crt-conio-l1-1-0.def",
"api-ms-win-crt-time-l1-1-0.def", "api-ms-win-crt-convert-l1-1-0.def.in",
"api-ms-win-crt-utility-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",
}
}
for _, name := range libs {
outpath := filepath.Join(tmpdir, filepath.Base(name)+".lib") outpath := filepath.Join(tmpdir, filepath.Base(name)+".lib")
inpath := filepath.Join(root, "lib/mingw-w64/mingw-w64-crt/lib-common/"+name) inpath := filepath.Join(root, "lib/mingw-w64/mingw-w64-crt/lib-common/"+name)
job := &compileJob{ job := &compileJob{
+13 -3
View File
@@ -398,15 +398,25 @@ func (c *Config) LibcCFlags() []string {
case "mingw-w64": case "mingw-w64":
root := goenv.Get("TINYGOROOT") root := goenv.Get("TINYGOROOT")
path := c.LibraryPath("mingw-w64") path := c.LibraryPath("mingw-w64")
return []string{ cflags := []string{
"-nostdlibinc", "-nostdlibinc",
"-isystem", filepath.Join(path, "include"), "-isystem", filepath.Join(path, "include"),
"-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "crt"), "-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "crt"),
"-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "include"), "-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "include"),
"-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "defaults", "include"), "-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "defaults", "include"),
"-D_UCRT",
"-D_WIN32_WINNT=0x0a00", // target Windows 10
} }
if c.GOARCH() == "386" {
cflags = append(cflags,
"-D__MSVCRT_VERSION__=0x700", // Microsoft Visual C++ .NET 2002
"-D_WIN32_WINNT=0x0501", // target Windows XP
)
} else {
cflags = append(cflags,
"-D_UCRT",
"-D_WIN32_WINNT=0x0a00", // target Windows 10
)
}
return cflags
case "": case "":
// No libc specified, nothing to add. // No libc specified, nothing to add.
return nil return nil
+2
View File
@@ -437,6 +437,8 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
case "386": case "386":
spec.LDFlags = append(spec.LDFlags, spec.LDFlags = append(spec.LDFlags,
"-m", "i386pe", "-m", "i386pe",
"--major-os-version", "4",
"--major-subsystem-version", "4",
) )
// __udivdi3 is not present in ucrt it seems. // __udivdi3 is not present in ucrt it seems.
spec.RTLib = "compiler-rt" spec.RTLib = "compiler-rt"
+1 -1
View File
@@ -208,7 +208,7 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
// > circumstances, and should not be exposed to source languages. // > circumstances, and should not be exposed to source languages.
llvmutil.AppendToGlobal(c.mod, "llvm.compiler.used", llvmFn) llvmutil.AppendToGlobal(c.mod, "llvm.compiler.used", llvmFn)
} }
case "GetModuleHandleExA", "GetProcAddress", "GetSystemInfo", "GetSystemTimeAsFileTime", "LoadLibraryExW", "QueryUnbiasedInterruptTime", "SetEnvironmentVariableA", "Sleep", "VirtualAlloc": case "GetModuleHandleExA", "GetProcAddress", "GetSystemInfo", "GetSystemTimeAsFileTime", "LoadLibraryExW", "QueryPerformanceCounter", "QueryPerformanceFrequency", "QueryUnbiasedInterruptTime", "SetEnvironmentVariableA", "Sleep", "SystemFunction036", "VirtualAlloc":
// On Windows we need to use a special calling convention for some // On Windows we need to use a special calling convention for some
// external calls. // external calls.
if c.GOOS == "windows" && c.GOARCH == "386" { if c.GOOS == "windows" && c.GOARCH == "386" {
+18 -21
View File
@@ -1,6 +1,9 @@
package rand package rand
import "errors" import (
"errors"
"unsafe"
)
func init() { func init() {
Reader = &reader{} Reader = &reader{}
@@ -16,28 +19,22 @@ func (r *reader) Read(b []byte) (n int, err error) {
return return
} }
var randomByte uint32 // Use the old RtlGenRandom, introduced in Windows XP.
for i := range b { // Even though the documentation says it is deprecated, it is widely used
// Call rand_s every four bytes because it's a C int (always 32-bit in // and probably won't go away anytime soon.
// Windows). // See for example: https://github.com/golang/go/issues/33542
if i%4 == 0 { // For Windows 7 and newer, we might switch to ProcessPrng in the future
errCode := libc_rand_s(&randomByte) // (which is a documented function and might be a tiny bit faster).
if errCode != 0 { ok := libc_RtlGenRandom(unsafe.Pointer(&b[0]), len(b))
// According to the documentation, it can return an error. if !ok {
return n, errRandom return 0, errRandom
}
} else {
randomByte >>= 8
}
b[i] = byte(randomByte)
} }
return len(b), nil return len(b), nil
} }
// Cryptographically secure random number generator. // This function is part of advapi32.dll, and is called SystemFunction036 for
// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/rand-s?view=msvc-170 // some reason. It's available on Windows XP and newer.
// errno_t rand_s(unsigned int* randomValue); // See: https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom
// //
//export rand_s //export SystemFunction036
func libc_rand_s(randomValue *uint32) int32 func libc_RtlGenRandom(buf unsafe.Pointer, len int) bool
+59 -16
View File
@@ -42,10 +42,24 @@ func __p___argc() *int32
//export __p___argv //export __p___argv
func __p___argv() **unsafe.Pointer func __p___argv() **unsafe.Pointer
type startupInfo struct {
newMode int32
}
//export __getmainargs
func __getmainargs(argc *int32, argv, env **unsafe.Pointer, doWildcard int, startInfo *startupInfo) int32
var performanceFrequency int64
//export mainCRTStartup //export mainCRTStartup
func mainCRTStartup() int { func mainCRTStartup() int {
preinit() preinit()
// Obtain the (constant) performance frequency when needed.
if GOARCH == "386" {
_QueryPerformanceFrequency(&performanceFrequency)
}
// Obtain the initial stack pointer right before calling the run() function. // Obtain the initial stack pointer right before calling the run() function.
// The run function has been moved to a separate (non-inlined) function so // The run function has been moved to a separate (non-inlined) function so
// that the correct stack pointer is read. // that the correct stack pointer is read.
@@ -78,9 +92,19 @@ var args []string
func os_runtime_args() []string { func os_runtime_args() []string {
if args == nil { if args == nil {
// Obtain argc/argv from the environment. // Obtain argc/argv from the environment.
_configure_narrow_argv(2) var argc int32
argc := *__p___argc() var argv *unsafe.Pointer
argv := *__p___argv() if GOARCH == "386" {
// MSVCRT.DLL
var env *unsafe.Pointer
startInfo := startupInfo{newMode: 0}
__getmainargs(&argc, &argv, &env, 1, &startInfo)
} else {
// UCRT
_configure_narrow_argv(2)
argc = *__p___argc()
argv = *__p___argv()
}
// Make args slice big enough so that it can store all command line // Make args slice big enough so that it can store all command line
// arguments. // arguments.
@@ -146,10 +170,30 @@ func sleepTicks(d timeUnit) {
} }
} }
//export QueryPerformanceFrequency
func _QueryPerformanceFrequency(*int64) bool
//export QueryPerformanceCounter
func _QueryPerformanceCounter(*int64) bool
func ticks() timeUnit { func ticks() timeUnit {
var unbiasedTime uint64 if GOARCH == "386" {
_QueryUnbiasedInterruptTime(&unbiasedTime) // Unfortunately QueryUnbiasedInterruptTime is only available starting
return timeUnit(unbiasedTime) // with Windows 7.
// Obtain counter (that runs at a fixed frequency).
var counter int64
_QueryPerformanceCounter(&counter)
// Convert this counter to ticks of 100ns (just like
// QueryUnbiasedInterruptTime).
// (We could also change the definition of ticks on GOOS=386 but that
// seems messy).
return timeUnit((counter * 10000000) / performanceFrequency)
} else {
var unbiasedTime uint64
_QueryUnbiasedInterruptTime(&unbiasedTime)
return timeUnit(unbiasedTime)
}
} }
//go:linkname now time.now //go:linkname now time.now
@@ -237,17 +281,16 @@ func procUnpin() {
} }
func hardwareRand() (n uint64, ok bool) { func hardwareRand() (n uint64, ok bool) {
var n1, n2 uint32 // Use the old RtlGenRandom, introduced in Windows XP.
errCode1 := libc_rand_s(&n1) // See the rationale in src/crypto/rand/rand_windows.go for why we use this
errCode2 := libc_rand_s(&n2) // one.
n = uint64(n1)<<32 | uint64(n2) ok = _RtlGenRandom(unsafe.Pointer(&n), 8)
ok = errCode1 == 0 && errCode2 == 0
return return
} }
// Cryptographically secure random number generator. // This function is part of advapi32.dll, and is called SystemFunction036 for
// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/rand-s?view=msvc-170 // some reason. It's available on Windows XP and newer.
// errno_t rand_s(unsigned int* randomValue); // See: https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom
// //
//export rand_s //export SystemFunction036
func libc_rand_s(randomValue *uint32) int32 func _RtlGenRandom(buf unsafe.Pointer, len int) bool