Skip to content

Commit e95d56f

Browse files
committed
windows: add windows/386 support
1 parent fdf075a commit e95d56f

File tree

12 files changed

+136
-34
lines changed

12 files changed

+136
-34
lines changed

.github/workflows/build-macos.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ jobs:
101101
- name: make gen-device
102102
run: make -j3 gen-device
103103
- name: Test TinyGo
104-
run: make test GOTESTFLAGS="-short"
104+
run: make test GOTESTFLAGS="-only-current-os"
105105
- name: Build TinyGo release tarball
106106
run: make release -j3
107107
- name: Test stdlib packages

.github/workflows/windows.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ jobs:
114114
run: make -j3 gen-device
115115
- name: Test TinyGo
116116
shell: bash
117-
run: make test GOTESTFLAGS="-short"
117+
run: make test GOTESTFLAGS="-only-current-os"
118118
- name: Build TinyGo release tarball
119119
shell: bash
120120
run: make build/release -j4

builder/builder_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ func TestClangAttributes(t *testing.T) {
6767
{GOOS: "linux", GOARCH: "mipsle", GOMIPS: "softfloat"},
6868
{GOOS: "darwin", GOARCH: "amd64"},
6969
{GOOS: "darwin", GOARCH: "arm64"},
70+
{GOOS: "windows", GOARCH: "386"},
7071
{GOOS: "windows", GOARCH: "amd64"},
7172
{GOOS: "windows", GOARCH: "arm64"},
7273
} {

builder/mingw-w64.go

+3
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ func makeMinGWExtraLibs(tmpdir, goarch string) []*compileJob {
9393
defpath := inpath
9494
var archDef, emulation string
9595
switch goarch {
96+
case "386":
97+
archDef = "-DDEF_I386"
98+
emulation = "i386pe"
9699
case "amd64":
97100
archDef = "-DDEF_X64"
98101
emulation = "i386pep"

compileopts/target.go

+12-6
Original file line numberDiff line numberDiff line change
@@ -430,14 +430,20 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
430430
spec.GC = "boehm"
431431
spec.Linker = "ld.lld"
432432
spec.Libc = "mingw-w64"
433-
// Note: using a medium code model, low image base and no ASLR
434-
// because Go doesn't really need those features. ASLR patches
435-
// around issues for unsafe languages like C/C++ that are not
436-
// normally present in Go (without explicitly opting in).
437-
// For more discussion:
438-
// https://groups.google.com/g/Golang-nuts/c/Jd9tlNc6jUE/m/Zo-7zIP_m3MJ?pli=1
439433
switch options.GOARCH {
434+
case "386":
435+
spec.LDFlags = append(spec.LDFlags,
436+
"-m", "i386pe",
437+
)
438+
// __udivdi3 is not present in ucrt it seems.
439+
spec.RTLib = "compiler-rt"
440440
case "amd64":
441+
// Note: using a medium code model, low image base and no ASLR
442+
// because Go doesn't really need those features. ASLR patches
443+
// around issues for unsafe languages like C/C++ that are not
444+
// normally present in Go (without explicitly opting in).
445+
// For more discussion:
446+
// https://groups.google.com/g/Golang-nuts/c/Jd9tlNc6jUE/m/Zo-7zIP_m3MJ?pli=1
441447
spec.LDFlags = append(spec.LDFlags,
442448
"-m", "i386pep",
443449
"--image-base", "0x400000",

compiler/calls.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,15 @@ func (b *builder) createCall(fnType llvm.Type, fn llvm.Value, args []llvm.Value,
7676
fragments := b.expandFormalParam(arg)
7777
expanded = append(expanded, fragments...)
7878
}
79-
return b.CreateCall(fnType, fn, expanded, name)
79+
call := b.CreateCall(fnType, fn, expanded, name)
80+
if !fn.IsAFunction().IsNil() {
81+
if cc := fn.FunctionCallConv(); cc != llvm.CCallConv {
82+
// Set a different calling convention if needed.
83+
// This is needed for GetModuleHandleExA on Windows, for example.
84+
call.SetInstructionCallConv(cc)
85+
}
86+
}
87+
return call
8088
}
8189

8290
// createInvoke is like createCall but continues execution at the landing pad if

compiler/llvm.go

+15
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,21 @@ func (b *builder) readStackPointer() llvm.Value {
452452
return b.CreateCall(stacksave.GlobalValueType(), stacksave, nil, "")
453453
}
454454

455+
// writeStackPointer emits a LLVM intrinsic call that updates the current stack
456+
// pointer.
457+
func (b *builder) writeStackPointer(sp llvm.Value) {
458+
name := "llvm.stackrestore.p0"
459+
if llvmutil.Version() < 18 {
460+
name = "llvm.stackrestore" // backwards compatibility with LLVM 17 and below
461+
}
462+
stackrestore := b.mod.NamedFunction(name)
463+
if stackrestore.IsNil() {
464+
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.dataPtrType}, false)
465+
stackrestore = llvm.AddFunction(b.mod, name, fnType)
466+
}
467+
b.CreateCall(stackrestore.GlobalValueType(), stackrestore, []llvm.Value{sp}, "")
468+
}
469+
455470
// createZExtOrTrunc lets the input value fit in the output type bits, by zero
456471
// extending or truncating the integer.
457472
func (b *builder) createZExtOrTrunc(value llvm.Value, t llvm.Type) llvm.Value {

compiler/symbol.go

+6
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,12 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
208208
// > circumstances, and should not be exposed to source languages.
209209
llvmutil.AppendToGlobal(c.mod, "llvm.compiler.used", llvmFn)
210210
}
211+
case "GetModuleHandleExA", "GetProcAddress", "GetSystemInfo", "GetSystemTimeAsFileTime", "LoadLibraryExW", "QueryUnbiasedInterruptTime", "SetEnvironmentVariableA", "Sleep", "VirtualAlloc":
212+
// On Windows we need to use a special calling convention for some
213+
// external calls.
214+
if c.GOOS == "windows" && c.GOARCH == "386" {
215+
llvmFn.SetFunctionCallConv(llvm.X86StdcallCallConv)
216+
}
211217
}
212218

213219
// External/exported functions may not retain pointer values.

compiler/syscall.go

+24-1
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,8 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
268268
// The signature looks like this:
269269
// func Syscall(trap, nargs, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)
270270

271+
isI386 := strings.HasPrefix(b.Triple, "i386-")
272+
271273
// Prepare input values.
272274
var paramTypes []llvm.Type
273275
var params []llvm.Value
@@ -285,11 +287,17 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
285287
if setLastError.IsNil() {
286288
llvmType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.ctx.Int32Type()}, false)
287289
setLastError = llvm.AddFunction(b.mod, "SetLastError", llvmType)
290+
if isI386 {
291+
setLastError.SetFunctionCallConv(llvm.X86StdcallCallConv)
292+
}
288293
}
289294
getLastError := b.mod.NamedFunction("GetLastError")
290295
if getLastError.IsNil() {
291296
llvmType := llvm.FunctionType(b.ctx.Int32Type(), nil, false)
292297
getLastError = llvm.AddFunction(b.mod, "GetLastError", llvmType)
298+
if isI386 {
299+
getLastError.SetFunctionCallConv(llvm.X86StdcallCallConv)
300+
}
293301
}
294302

295303
// Now do the actual call. Pseudocode:
@@ -300,9 +308,24 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
300308
// Note that SetLastError/GetLastError could be replaced with direct
301309
// access to the thread control block, which is probably smaller and
302310
// faster. The Go runtime does this in assembly.
303-
b.CreateCall(setLastError.GlobalValueType(), setLastError, []llvm.Value{llvm.ConstNull(b.ctx.Int32Type())}, "")
311+
// On windows/386, we also need to save/restore the stack pointer. I'm
312+
// not entirely sure why this is needed, but without it these calls
313+
// change the stack pointer leading to a crash soon after.
314+
setLastErrorCall := b.CreateCall(setLastError.GlobalValueType(), setLastError, []llvm.Value{llvm.ConstNull(b.ctx.Int32Type())}, "")
315+
var sp llvm.Value
316+
if isI386 {
317+
setLastErrorCall.SetInstructionCallConv(llvm.X86StdcallCallConv)
318+
sp = b.readStackPointer()
319+
}
304320
syscallResult := b.CreateCall(llvmType, fnPtr, params, "")
321+
if isI386 {
322+
syscallResult.SetInstructionCallConv(llvm.X86StdcallCallConv)
323+
b.writeStackPointer(sp)
324+
}
305325
errResult := b.CreateCall(getLastError.GlobalValueType(), getLastError, nil, "err")
326+
if isI386 {
327+
errResult.SetInstructionCallConv(llvm.X86StdcallCallConv)
328+
}
306329
if b.uintptrType != b.ctx.Int32Type() {
307330
errResult = b.CreateZExt(errResult, b.uintptrType, "err.uintptr")
308331
}

main_test.go

+36-24
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ const TESTDATA = "testdata"
3535

3636
var testTarget = flag.String("target", "", "override test target")
3737

38+
var testOnlyCurrentOS = flag.Bool("only-current-os", false, "")
39+
3840
var supportedLinuxArches = map[string]string{
3941
"AMD64Linux": "linux/amd64",
4042
"X86Linux": "linux/386",
@@ -158,20 +160,35 @@ func TestBuild(t *testing.T) {
158160
return
159161
}
160162

161-
t.Run("EmulatedCortexM3", func(t *testing.T) {
162-
t.Parallel()
163-
runPlatTests(optionsFromTarget("cortex-m-qemu", sema), tests, t)
164-
})
163+
if !*testOnlyCurrentOS {
164+
t.Run("EmulatedCortexM3", func(t *testing.T) {
165+
t.Parallel()
166+
runPlatTests(optionsFromTarget("cortex-m-qemu", sema), tests, t)
167+
})
165168

166-
t.Run("EmulatedRISCV", func(t *testing.T) {
167-
t.Parallel()
168-
runPlatTests(optionsFromTarget("riscv-qemu", sema), tests, t)
169-
})
169+
t.Run("EmulatedRISCV", func(t *testing.T) {
170+
t.Parallel()
171+
runPlatTests(optionsFromTarget("riscv-qemu", sema), tests, t)
172+
})
170173

171-
t.Run("AVR", func(t *testing.T) {
172-
t.Parallel()
173-
runPlatTests(optionsFromTarget("simavr", sema), tests, t)
174-
})
174+
t.Run("AVR", func(t *testing.T) {
175+
t.Parallel()
176+
runPlatTests(optionsFromTarget("simavr", sema), tests, t)
177+
})
178+
179+
t.Run("WebAssembly", func(t *testing.T) {
180+
t.Parallel()
181+
runPlatTests(optionsFromTarget("wasm", sema), tests, t)
182+
})
183+
t.Run("WASI", func(t *testing.T) {
184+
t.Parallel()
185+
runPlatTests(optionsFromTarget("wasip1", sema), tests, t)
186+
})
187+
t.Run("WASIp2", func(t *testing.T) {
188+
t.Parallel()
189+
runPlatTests(optionsFromTarget("wasip2", sema), tests, t)
190+
})
191+
}
175192

176193
if runtime.GOOS == "linux" {
177194
for name, osArch := range supportedLinuxArches {
@@ -191,18 +208,13 @@ func TestBuild(t *testing.T) {
191208
options := optionsFromOSARCH("linux/mipsle/softfloat", sema)
192209
runTest("cgo/", options, t, nil, nil)
193210
})
194-
t.Run("WebAssembly", func(t *testing.T) {
195-
t.Parallel()
196-
runPlatTests(optionsFromTarget("wasm", sema), tests, t)
197-
})
198-
t.Run("WASI", func(t *testing.T) {
199-
t.Parallel()
200-
runPlatTests(optionsFromTarget("wasip1", sema), tests, t)
201-
})
202-
t.Run("WASIp2", func(t *testing.T) {
203-
t.Parallel()
204-
runPlatTests(optionsFromTarget("wasip2", sema), tests, t)
205-
})
211+
} else if runtime.GOOS == "windows" {
212+
if runtime.GOARCH != "386" {
213+
t.Run("Windows386", func(t *testing.T) {
214+
t.Parallel()
215+
runPlatTests(optionsFromOSARCH("windows/386", sema), tests, t)
216+
})
217+
}
206218
}
207219
}
208220

src/internal/task/task_stack_386.S

+14
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
#ifdef _WIN32
2+
.global _tinygo_startTask
3+
_tinygo_startTask:
4+
#else // Linux etc
15
.section .text.tinygo_startTask
26
.global tinygo_startTask
37
.type tinygo_startTask, %function
48
tinygo_startTask:
9+
#endif
510
.cfi_startproc
611
// Small assembly stub for starting a goroutine. This is already run on the
712
// new stack, with the callee-saved registers already loaded.
@@ -24,12 +29,21 @@ tinygo_startTask:
2429
addl $4, %esp
2530

2631
// After return, exit this goroutine. This is a tail call.
32+
#ifdef _WIN32
33+
jmp _tinygo_pause
34+
#else
2735
jmp tinygo_pause
36+
#endif
2837
.cfi_endproc
2938

39+
#ifdef _WIN32
40+
.global _tinygo_swapTask
41+
_tinygo_swapTask:
42+
#else
3043
.global tinygo_swapTask
3144
.type tinygo_swapTask, %function
3245
tinygo_swapTask:
46+
#endif
3347
// This function gets the following parameters:
3448
movl 4(%esp), %eax // newStack uintptr
3549
movl 8(%esp), %ecx // oldStack *uintptr

src/runtime/asm_386.S

+14
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
#ifdef _WIN32
2+
.global _tinygo_scanCurrentStack
3+
_tinygo_scanCurrentStack:
4+
#else
15
.section .text.tinygo_scanCurrentStack
26
.global tinygo_scanCurrentStack
37
.type tinygo_scanCurrentStack, %function
48
tinygo_scanCurrentStack:
9+
#endif
510
// Sources:
611
// * https://stackoverflow.com/questions/18024672/what-registers-are-preserved-through-a-linux-x86-64-function-call
712
// * https://godbolt.org/z/q7e8dn
@@ -15,17 +20,26 @@ tinygo_scanCurrentStack:
1520
// Scan the stack.
1621
subl $8, %esp // adjust the stack before the call to maintain 16-byte alignment
1722
pushl %esp
23+
#ifdef _WIN32
24+
calll _tinygo_scanstack
25+
#else
1826
calll tinygo_scanstack
27+
#endif
1928

2029
// Restore the stack pointer. Registers do not need to be restored as they
2130
// were only pushed to be discoverable by the GC.
2231
addl $28, %esp
2332
retl
2433

2534

35+
#ifdef _WIN32
36+
.global _tinygo_longjmp
37+
_tinygo_longjmp:
38+
#else
2639
.section .text.tinygo_longjmp
2740
.global tinygo_longjmp
2841
tinygo_longjmp:
42+
#endif
2943
// Note: the code we jump to assumes eax is set to a non-zero value if we
3044
// jump from here.
3145
movl 4(%esp), %eax

0 commit comments

Comments
 (0)