diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 6821ca92..c7d40cd8 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -23,3 +23,89 @@ jobs: - name: Build run: go build -v ./... + + llgo-test: + name: llgo-test (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - macos-latest + - windows-2022 + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.26.7' + + - name: Check out LLGo + uses: actions/checkout@v4 + with: + repository: xgo-dev/llgo + # Includes the merged stdcall support required by the direct Winsock + # declarations while keeping this library test independent of R9. + ref: 6fc2ef150f95d8b9331433f325fcb1b6ee5ad4e4 + path: .llgo + + - name: Set up LLGo dependencies + uses: ./.llgo/.github/actions/setup-deps + with: + llvm-version: '19' + + - name: Build LLGo + if: runner.os != 'Windows' + working-directory: .llgo + run: go build -tags=dev -o "$RUNNER_TEMP/llgo" ./cmd/llgo + + - name: Test LLGo native bindings + if: runner.os != 'Windows' + env: + LLGO_BUILD_CACHE: 'off' + LLGO_ROOT: ${{ github.workspace }}/.llgo + run: | + "$RUNNER_TEMP/llgo" test -count=1 -v ./c ./c/math/rand ./c/net ./c/os ./c/setjmp ./c/time ./cpp/std + + # The Unix binding exposes libc++'s C++ ABI and is therefore exercised on + # macOS. Linux LLVM packages use libstdc++; Windows is tested below through + # its stable C bridge. + - name: Test LLGo LLVM demangle bindings + if: runner.os == 'macOS' + env: + LLGO_BUILD_CACHE: 'off' + LLGO_ROOT: ${{ github.workspace }}/.llgo + run: | + "$RUNNER_TEMP/llgo" test -v ./cpp/llvm + + - name: Build LLGo on Windows + if: runner.os == 'Windows' + working-directory: .llgo + shell: pwsh + run: go build -tags=dev -o "$env:RUNNER_TEMP\llgo.exe" ./cmd/llgo + + - name: Test LLGo Windows bindings + if: runner.os == 'Windows' + shell: pwsh + env: + LLGO_BUILD_CACHE: 'off' + LLGO_REQUIRE_MSVC: '1' + LLGO_ROOT: ${{ github.workspace }}/.llgo + run: | + $ErrorActionPreference = "Stop" + $llgo = Join-Path $env:RUNNER_TEMP "llgo.exe" + + & $llgo test -count=1 -v ` + ./c ` + ./c/math/rand ` + ./c/net ` + ./c/os ` + ./c/setjmp ` + ./c/time ` + ./cpp/llvm ` + ./cpp/std + if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE + } diff --git a/c/c.go b/c/c.go index 8408dd97..6e7d8d8d 100644 --- a/c/c.go +++ b/c/c.go @@ -20,10 +20,6 @@ import ( "unsafe" ) -const ( - LLGoPackage = "decl" -) - type ( Void = [0]byte Char = int8 diff --git a/c/math/rand/rand.go b/c/math/rand/rand.go index 833eb5af..c9c0d7e8 100644 --- a/c/math/rand/rand.go +++ b/c/math/rand/rand.go @@ -22,33 +22,10 @@ import ( "github.com/goplus/lib/c" ) -const ( - LLGoPackage = "decl" -) - // ----------------------------------------------------------------------------- //go:linkname Rand C.rand func Rand() c.Int -//go:linkname RandR C.rand_r -func RandR(*c.Uint) c.Int - //go:linkname Srand C.srand func Srand(c.Uint) - -//go:linkname Sranddev C.sranddev -func Sranddev() - -// ----------------------------------------------------------------------------- - -//go:linkname Random C.random -func Random() c.Long - -//go:linkname Srandom C.srandom -func Srandom(c.Uint) - -//go:linkname Srandomdev C.srandomdev -func Srandomdev() - -// ----------------------------------------------------------------------------- diff --git a/c/math/rand/rand_default.go b/c/math/rand/rand_default.go new file mode 100644 index 00000000..063582f5 --- /dev/null +++ b/c/math/rand/rand_default.go @@ -0,0 +1,28 @@ +//go:build !windows + +// Copyright (c) 2026 The GoPlus Authors. Licensed under the Apache License 2.0. + +package rand + +import ( + _ "unsafe" + + "github.com/goplus/lib/c" +) + +const LLGoPackage = "decl" + +//go:linkname RandR C.rand_r +func RandR(*c.Uint) c.Int + +//go:linkname Sranddev C.sranddev +func Sranddev() + +//go:linkname Random C.random +func Random() c.Long + +//go:linkname Srandom C.srandom +func Srandom(c.Uint) + +//go:linkname Srandomdev C.srandomdev +func Srandomdev() diff --git a/c/math/rand/rand_windows.go b/c/math/rand/rand_windows.go new file mode 100644 index 00000000..a405de1b --- /dev/null +++ b/c/math/rand/rand_windows.go @@ -0,0 +1,9 @@ +//go:build windows + +// Copyright (c) 2026 The GoPlus Authors. Licensed under the Apache License 2.0. + +package rand + +// The Universal CRT provides only the ISO C rand and srand functions declared +// in rand.go. BSD/POSIX extensions remain in rand_default.go. +const LLGoPackage = "decl" diff --git a/c/math/rand/rand_windows_llgo_test.go b/c/math/rand/rand_windows_llgo_test.go new file mode 100644 index 00000000..21640663 --- /dev/null +++ b/c/math/rand/rand_windows_llgo_test.go @@ -0,0 +1,20 @@ +//go:build llgo && windows + +// Copyright (c) 2026 The GoPlus Authors. Licensed under the Apache License 2.0. + +package rand_test + +import ( + "testing" + + "github.com/goplus/lib/c/math/rand" +) + +func TestWindowsRand(t *testing.T) { + rand.Srand(1) + first := rand.Rand() + rand.Srand(1) + if got := rand.Rand(); got != first { + t.Fatalf("rand after equal seeds = %d, want %d", got, first) + } +} diff --git a/c/net/addrinfo_windows.go b/c/net/addrinfo_windows.go new file mode 100644 index 00000000..5546405f --- /dev/null +++ b/c/net/addrinfo_windows.go @@ -0,0 +1,20 @@ +//go:build windows + +// Copyright (c) 2026 The GoPlus Authors. Licensed under the Apache License 2.0. + +package net + +import "github.com/goplus/lib/c" + +// AddrInfo matches the Winsock ADDRINFOA layout. ai_addrlen is size_t on +// Windows, unlike the socklen_t field used by Unix implementations. +type AddrInfo struct { + Flags c.Int + Family c.Int + SockType c.Int + Protocol c.Int + AddrLen uintptr + CanOnName *c.Char + Addr *SockAddr + Next *AddrInfo +} diff --git a/c/net/functions_windows.go b/c/net/functions_windows.go new file mode 100644 index 00000000..54e2d7a5 --- /dev/null +++ b/c/net/functions_windows.go @@ -0,0 +1,77 @@ +//go:build windows + +// Copyright (c) 2026 The GoPlus Authors. Licensed under the Apache License 2.0. + +package net + +import ( + _ "unsafe" + + "github.com/goplus/lib/c" +) + +// Winsock uses stdcall on Windows/386. On Windows/AMD64 and Windows/ARM64, +// stdcall maps to the platform's unified native C calling convention. + +//go:linkname WSAStartup stdcall.WSAStartup +func WSAStartup(version uint16, data *WSAData) c.Int + +//go:linkname WSACleanup stdcall.WSACleanup +func WSACleanup() c.Int + +//go:linkname Getaddrinfo stdcall.getaddrinfo +func Getaddrinfo(host *c.Char, port *c.Char, addrInfo *AddrInfo, result **AddrInfo) c.Int + +//go:linkname Freeaddrinfo stdcall.freeaddrinfo +func Freeaddrinfo(addrInfo *AddrInfo) + +//go:linkname Socket stdcall.socket +func Socket(domain c.Int, typ c.Int, protocol c.Int) SocketT + +//go:linkname Bind stdcall.bind +func Bind(sockfd SocketT, addr *SockAddr, addrlen SocklenT) c.Int + +//go:linkname Connect stdcall.connect +func Connect(sockfd SocketT, addr *SockAddr, addrlen SocklenT) c.Int + +//go:linkname Listen stdcall.listen +func Listen(sockfd SocketT, backlog c.Int) c.Int + +//go:linkname Accept stdcall.accept +func Accept(sockfd SocketT, addr *SockAddr, addrlen *SocklenT) SocketT + +//go:linkname Closesocket stdcall.closesocket +func Closesocket(sockfd SocketT) c.Int + +//go:linkname GetHostByName stdcall.gethostbyname +func GetHostByName(name *c.Char) *Hostent + +//go:linkname InetNtop stdcall.inet_ntop +func InetNtop(af c.Int, src c.Pointer, dst *c.Char, size uintptr) *c.Char + +//go:linkname InetAddr stdcall.inet_addr +func InetAddr(value *c.Char) c.Uint + +//go:linkname Send stdcall.send +func Send(sockfd SocketT, buffer c.Pointer, length c.Int, flags c.Int) c.Int + +//go:linkname Recv stdcall.recv +func Recv(sockfd SocketT, buffer c.Pointer, length c.Int, flags c.Int) c.Int + +//go:linkname SetSockOpt stdcall.setsockopt +func SetSockOpt(sockfd SocketT, level c.Int, optionName c.Int, optionValue c.Pointer, optionLength SocklenT) c.Int + +//go:linkname Ntohs stdcall.ntohs +func Ntohs(value uint16) uint16 + +//go:linkname Htons stdcall.htons +func Htons(value uint16) uint16 + +//go:linkname Ntohl stdcall.ntohl +func Ntohl(value c.Uint) c.Uint + +//go:linkname Htonl stdcall.htonl +func Htonl(value c.Uint) c.Uint + +//go:linkname WSAGetLastError stdcall.WSAGetLastError +func WSAGetLastError() c.Int diff --git a/c/net/net.go b/c/net/net.go index 201d09de..aa621154 100644 --- a/c/net/net.go +++ b/c/net/net.go @@ -1,3 +1,5 @@ +//go:build !windows + /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. * @@ -218,6 +220,6 @@ type AddrInfo struct { func Getaddrinfo(host *c.Char, port *c.Char, addrInfo *AddrInfo, result **AddrInfo) c.Int //go:linkname Freeaddrinfo C.freeaddrinfo -func Freeaddrinfo(addrInfo *AddrInfo) c.Int +func Freeaddrinfo(addrInfo *AddrInfo) // ----------------------------------------------------------------------------- diff --git a/c/net/net_windows.go b/c/net/net_windows.go new file mode 100644 index 00000000..c6b6fcca --- /dev/null +++ b/c/net/net_windows.go @@ -0,0 +1,117 @@ +//go:build windows + +/* + * Copyright (c) 2026 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net + +import "github.com/goplus/lib/c" + +const LLGoPackage = "link: -lws2_32" + +type ( + SocketT = uintptr + SocklenT = c.Int +) + +const ( + InvalidSocket SocketT = ^SocketT(0) + SocketError = -1 +) + +func MakeWord(low, high byte) uint16 { + return uint16(low) | uint16(high)<<8 +} + +const ( + AF_UNSPEC = 0 + AF_UNIX = 1 + AF_LOCAL = AF_UNIX + AF_INET = 2 + AF_IPX = 6 + AF_APPLETALK = 16 + AF_NETBIOS = 17 + AF_INET6 = 23 + AF_IRDA = 26 + AF_BTH = 32 + AF_MAX = 34 +) + +const ( + SOCK_STREAM = 1 + SOCK_DGRAM = 2 + SOCK_RAW = 3 + SOCK_RDM = 4 + SOCK_SEQPACKET = 5 +) + +const ( + EAI_AGAIN = 11002 + EAI_BADFLAGS = 10022 + EAI_FAIL = 11003 + EAI_FAMILY = 10047 + EAI_MEMORY = 8 + EAI_NONAME = 11001 + EAI_SERVICE = 10109 + EAI_SOCKTYPE = 10044 +) + +const INET_ADDRSTRLEN = 16 + +// SockaddrIn matches the Winsock SOCKADDR_IN layout. Unlike BSD sockaddr_in, +// it has no leading length byte and its address family is 16 bits. +type SockaddrIn struct { + Family uint16 + Port uint16 + Addr InAddr + Zero [8]c.Char +} + +type SockaddrIn6 struct { + Family uint16 + Port uint16 + Flowinfo c.Uint + Addr In6Addr + ScopeId c.Uint +} + +type SockaddrStorage struct { + Family uint16 + pad1 [6]c.Char + align c.LongLong + pad2 [112]c.Char +} + +type InAddr struct { + Addr c.Uint +} + +type In6Addr struct { + U6Addr [16]uint8 +} + +type SockAddr struct { + Family uint16 + Data [14]c.Char +} + +type Hostent struct { + Name *c.Char + Aliases **c.Char + AddrType int16 + Length int16 + AddrList **c.Char +} diff --git a/c/net/net_windows_llgo_test.go b/c/net/net_windows_llgo_test.go new file mode 100644 index 00000000..192bfc36 --- /dev/null +++ b/c/net/net_windows_llgo_test.go @@ -0,0 +1,35 @@ +//go:build llgo && windows + +// Copyright (c) 2026 The GoPlus Authors. Licensed under the Apache License 2.0. + +package net_test + +import ( + "testing" + + llnet "github.com/goplus/lib/c/net" +) + +func TestWinsockLifecycle(t *testing.T) { + var data llnet.WSAData + if result := llnet.WSAStartup(llnet.MakeWord(2, 2), &data); result != 0 { + t.Fatalf("WSAStartup returned %d", result) + } + t.Cleanup(func() { + if result := llnet.WSACleanup(); result != 0 { + t.Errorf("WSACleanup returned %d", result) + } + }) + + if data.Version != llnet.MakeWord(2, 2) { + t.Fatalf("WSAStartup selected version %#x, want %#x", data.Version, llnet.MakeWord(2, 2)) + } + + socket := llnet.Socket(llnet.AF_INET, llnet.SOCK_STREAM, 0) + if socket == llnet.InvalidSocket { + t.Fatalf("socket failed with Winsock error %d", llnet.WSAGetLastError()) + } + if result := llnet.Closesocket(socket); result != 0 { + t.Fatalf("closesocket returned %d (Winsock error %d)", result, llnet.WSAGetLastError()) + } +} diff --git a/c/net/net_windows_test.go b/c/net/net_windows_test.go new file mode 100644 index 00000000..32acc353 --- /dev/null +++ b/c/net/net_windows_test.go @@ -0,0 +1,41 @@ +//go:build windows + +// Copyright (c) 2026 The GoPlus Authors. Licensed under the Apache License 2.0. + +package net + +import ( + "testing" + "unsafe" +) + +func TestWinsockLayouts(t *testing.T) { + ptrSize := unsafe.Sizeof(uintptr(0)) + tests := []struct { + name string + got uintptr + want uintptr + }{ + {"SockaddrIn", unsafe.Sizeof(SockaddrIn{}), 16}, + {"SockaddrIn6", unsafe.Sizeof(SockaddrIn6{}), 28}, + {"SockaddrStorage", unsafe.Sizeof(SockaddrStorage{}), 128}, + {"SockAddr", unsafe.Sizeof(SockAddr{}), 16}, + {"Hostent.AddrType", unsafe.Offsetof(Hostent{}.AddrType), 2 * ptrSize}, + {"Hostent.Length", unsafe.Offsetof(Hostent{}.Length), 2*ptrSize + 2}, + {"Hostent.AddrList", unsafe.Offsetof(Hostent{}.AddrList), align(2*ptrSize+4, ptrSize)}, + {"AddrInfo.AddrLen", unsafe.Offsetof(AddrInfo{}.AddrLen), 16}, + {"AddrInfo.CanOnName", unsafe.Offsetof(AddrInfo{}.CanOnName), 16 + ptrSize}, + {"AddrInfo.Addr", unsafe.Offsetof(AddrInfo{}.Addr), 16 + 2*ptrSize}, + {"AddrInfo.Next", unsafe.Offsetof(AddrInfo{}.Next), 16 + 3*ptrSize}, + {"WSAData", unsafe.Sizeof(WSAData{}), wsaDataSize}, + } + for _, test := range tests { + if test.got != test.want { + t.Errorf("%s = %d, want %d", test.name, test.got, test.want) + } + } +} + +func align(value, alignment uintptr) uintptr { + return (value + alignment - 1) &^ (alignment - 1) +} diff --git a/c/net/wsadata_windows_386.go b/c/net/wsadata_windows_386.go new file mode 100644 index 00000000..eea56db6 --- /dev/null +++ b/c/net/wsadata_windows_386.go @@ -0,0 +1,18 @@ +//go:build windows && 386 + +package net + +import "github.com/goplus/lib/c" + +const wsaDataSize = 400 + +// WSAData matches the 32-bit Winsock WSADATA layout. +type WSAData struct { + Version uint16 + HighVersion uint16 + Description [257]c.Char + SystemStatus [129]c.Char + MaxSockets uint16 + MaxUdpDg uint16 + VendorInfo *c.Char +} diff --git a/c/net/wsadata_windows_64.go b/c/net/wsadata_windows_64.go new file mode 100644 index 00000000..eb100fa4 --- /dev/null +++ b/c/net/wsadata_windows_64.go @@ -0,0 +1,18 @@ +//go:build windows && !386 + +package net + +import "github.com/goplus/lib/c" + +const wsaDataSize = 408 + +// WSAData matches the 64-bit Winsock WSADATA layout. +type WSAData struct { + Version uint16 + HighVersion uint16 + MaxSockets uint16 + MaxUdpDg uint16 + VendorInfo *c.Char + Description [257]c.Char + SystemStatus [129]c.Char +} diff --git a/c/os/dir.go b/c/os/dir.go index ba4569ef..bd3cf9e2 100644 --- a/c/os/dir.go +++ b/c/os/dir.go @@ -1,3 +1,5 @@ +//go:build !windows + package os import ( diff --git a/c/os/dir_unix.go b/c/os/dir_unix.go index e6208b89..45e1f498 100644 --- a/c/os/dir_unix.go +++ b/c/os/dir_unix.go @@ -1,5 +1,6 @@ -//go:build !(darwin && amd64) +//go:build !(darwin && amd64) && !windows // +build !darwin !amd64 +// +build !windows package os diff --git a/c/os/flags_default.go b/c/os/flags_default.go new file mode 100644 index 00000000..e4e55097 --- /dev/null +++ b/c/os/flags_default.go @@ -0,0 +1,31 @@ +//go:build !windows + +/* + * Copyright (c) 2026 The GoPlus Authors (goplus.org). All rights reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package os + +const ( + F_GETFL = 3 + F_SETFL = 4 + + O_RDONLY = 0x0000 + O_WRONLY = 0x0001 + O_RDWR = 0x0002 + O_ACCMODE = 0x0003 + O_NONBLOCK = 0x00000004 + O_CREAT = 0x00000200 + O_TRUNC = 0x00000400 + + EAGAIN = 35 +) diff --git a/c/os/functions_default.go b/c/os/functions_default.go new file mode 100644 index 00000000..edf08e26 --- /dev/null +++ b/c/os/functions_default.go @@ -0,0 +1,43 @@ +//go:build !windows + +/* + * Copyright (c) 2026 The GoPlus Authors (goplus.org). All rights reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package os + +import ( + _ "unsafe" + + "github.com/goplus/lib/c" +) + +//go:linkname Getcwd C.getcwd +func Getcwd(buffer c.Pointer, size uintptr) *c.Char + +//go:linkname Open C.open +func Open(path *c.Char, flags c.Int, __llgo_va_list ...any) c.Int + +//go:linkname Fcntl C.fcntl +func Fcntl(a c.Int, b c.Int, __llgo_va_list ...any) c.Int + +//go:linkname Close C.close +func Close(fd c.Int) c.Int + +//go:linkname Read C.read +func Read(fd c.Int, buf c.Pointer, count uintptr) int + +//go:linkname Write C.write +func Write(fd c.Int, buf c.Pointer, count uintptr) int + +//go:linkname Execlp C.execlp +func Execlp(file *c.Char, arg0 *c.Char, __llgo_va_list ...any) c.Int diff --git a/c/os/os.go b/c/os/os.go index cd126447..8f44c61f 100644 --- a/c/os/os.go +++ b/c/os/os.go @@ -1,3 +1,5 @@ +//go:build !windows + /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. * @@ -23,33 +25,6 @@ import ( "github.com/goplus/lib/c/syscall" ) -const ( - /* get file status flags */ - F_GETFL = 3 - /* set file status flags */ - F_SETFL = 4 - - /* open for reading only */ - O_RDONLY = 0x0000 - /* open for writing only */ - O_WRONLY = 0x0001 - /* open for reading and writing */ - O_RDWR = 0x0002 - /* mask for above modes */ - O_ACCMODE = 0x0003 - - /* no delay */ - O_NONBLOCK = 0x00000004 - /* create if nonexistant */ - O_CREAT = 0x00000200 - /* truncate to zero length */ - O_TRUNC = 0x00000400 -) - -const ( - EAGAIN = 35 -) - type ( StatT = syscall.Stat_t ) @@ -95,9 +70,6 @@ func Chmod(path *c.Char, mode ModeT) c.Int // ----------------------------------------------------------------------------- -//go:linkname Getcwd C.getcwd -func Getcwd(buffer c.Pointer, size uintptr) *c.Char - //go:linkname Chdir C.chdir func Chdir(path *c.Char) c.Int @@ -137,18 +109,12 @@ func Fchownat(dirfd c.Int, path *c.Char, owner UidT, group GidT, flags c.Int) c. // ----------------------------------------------------------------------------- -//go:linkname Open C.open -func Open(path *c.Char, flags c.Int, __llgo_va_list ...any) c.Int - //go:linkname Openat C.openat func Openat(dirfd c.Int, path *c.Char, flags c.Int, mode ModeT) c.Int //go:linkname Creat C.creat func Creat(path *c.Char, mode ModeT) c.Int -//go:linkname Fcntl C.fcntl -func Fcntl(a c.Int, b c.Int, __llgo_va_list ...any) c.Int - //go:linkname Dup C.dup func Dup(fd c.Int) c.Int @@ -164,15 +130,6 @@ func Mkfifo(path *c.Char, mode ModeT) c.Int //go:linkname Mknod C.mknod func Mknod(path *c.Char, mode ModeT, dev DevT) c.Int -//go:linkname Close C.close -func Close(fd c.Int) c.Int - -//go:linkname Read C.read -func Read(fd c.Int, buf c.Pointer, count uintptr) int - -//go:linkname Write C.write -func Write(fd c.Int, buf c.Pointer, count uintptr) int - //go:linkname Lseek C.lseek func Lseek(fd c.Int, offset OffT, whence c.Int) OffT @@ -210,9 +167,6 @@ func Execle(path *c.Char, arg0 *c.Char, __llgo_va_list ...any) c.Int // Execlp only needs to provide the program name and it will search for the program in the // paths specified in the PATH environment variable. // -//go:linkname Execlp C.execlp -func Execlp(file *c.Char, arg0 *c.Char, __llgo_va_list ...any) c.Int - //go:linkname Execv C.execv func Execv(path *c.Char, argv **c.Char) c.Int diff --git a/c/os/os_nonwasm.go b/c/os/os_nonwasm.go index 2a4fed0c..486c29e5 100644 --- a/c/os/os_nonwasm.go +++ b/c/os/os_nonwasm.go @@ -1,4 +1,4 @@ -//go:build !wasm +//go:build !wasm && !windows /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. diff --git a/c/os/os_other.go b/c/os/os_other.go index 4c3b1cc6..df53a728 100644 --- a/c/os/os_other.go +++ b/c/os/os_other.go @@ -1,4 +1,4 @@ -//go:build !darwin +//go:build !darwin && !windows /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. diff --git a/c/os/os_windows.go b/c/os/os_windows.go new file mode 100644 index 00000000..1682baa1 --- /dev/null +++ b/c/os/os_windows.go @@ -0,0 +1,123 @@ +//go:build windows + +/* + * Copyright (c) 2026 The GoPlus Authors (goplus.org). All rights reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package os + +import ( + _ "unsafe" + + "github.com/goplus/lib/c" +) + +const ( + LLGoPackage = "link" + PATH_MAX = 260 +) + +type ( + ModeT c.Int + OffT int64 + PidT c.Int +) + +const ( + O_RDONLY = 0x0000 + O_WRONLY = 0x0001 + O_RDWR = 0x0002 + O_APPEND = 0x0008 + O_CREAT = 0x0100 + O_TRUNC = 0x0200 + O_EXCL = 0x0400 + O_TEXT = 0x4000 + O_BINARY = 0x8000 + + EAGAIN = 11 +) + +//go:linkname errno C._errno +func errno() *c.Int + +func Errno() c.Int { + return *errno() +} + +//go:linkname Remove C.remove +func Remove(path *c.Char) c.Int + +//go:linkname Rename C.rename +func Rename(oldpath *c.Char, newpath *c.Char) c.Int + +//go:linkname Chdir C._chdir +func Chdir(path *c.Char) c.Int + +//go:linkname Getenv C.getenv +func Getenv(name *c.Char) *c.Char + +//go:linkname Putenv C._putenv +func Putenv(env *c.Char) c.Int + +//go:linkname Getcwd C._getcwd +func Getcwd(buffer c.Pointer, size c.Int) *c.Char + +//go:linkname Open C._open +func Open(path *c.Char, flags c.Int, __llgo_va_list ...any) c.Int + +//go:linkname Creat C._creat +func Creat(path *c.Char, mode ModeT) c.Int + +//go:linkname Dup C._dup +func Dup(fd c.Int) c.Int + +//go:linkname Dup2 C._dup2 +func Dup2(oldfd c.Int, newfd c.Int) c.Int + +//go:linkname Close C._close +func Close(fd c.Int) c.Int + +//go:linkname Read C._read +func Read(fd c.Int, buf c.Pointer, count c.Uint) c.Int + +//go:linkname Write C._write +func Write(fd c.Int, buf c.Pointer, count c.Uint) c.Int + +//go:linkname Lseek C._lseeki64 +func Lseek(fd c.Int, offset OffT, whence c.Int) OffT + +//go:linkname Isatty C._isatty +func Isatty(fd c.Int) c.Int + +//go:linkname Execl C._execl +func Execl(path *c.Char, arg0 *c.Char, __llgo_va_list ...any) c.Int + +//go:linkname Execle C._execle +func Execle(path *c.Char, arg0 *c.Char, __llgo_va_list ...any) c.Int + +//go:linkname Execlp C._execlp +func Execlp(file *c.Char, arg0 *c.Char, __llgo_va_list ...any) c.Int + +//go:linkname Execv C._execv +func Execv(path *c.Char, argv **c.Char) c.Int + +//go:linkname Execve C._execve +func Execve(path *c.Char, argv **c.Char, envp **c.Char) c.Int + +//go:linkname Execvp C._execvp +func Execvp(file *c.Char, argv **c.Char) c.Int + +//go:linkname Getpid C._getpid +func Getpid() PidT + +//go:linkname Exit C.exit +func Exit(status c.Int) diff --git a/c/os/os_windows_llgo_test.go b/c/os/os_windows_llgo_test.go new file mode 100644 index 00000000..37fdb97d --- /dev/null +++ b/c/os/os_windows_llgo_test.go @@ -0,0 +1,64 @@ +//go:build llgo && windows + +// Copyright (c) 2026 The GoPlus Authors. Licensed under the Apache License 2.0. + +package os_test + +import ( + "testing" + "unsafe" + + "github.com/goplus/lib/c" + llos "github.com/goplus/lib/c/os" +) + +func TestWindowsStatLayout(t *testing.T) { + var value llos.StatT + if got := unsafe.Sizeof(value); got != 56 { + t.Fatalf("sizeof(_stat64) = %d, want 56", got) + } + if got := unsafe.Alignof(value); got != 8 { + t.Fatalf("alignof(_stat64) = %d, want 8", got) + } +} + +func TestWindowsGetcwd(t *testing.T) { + buffer := make([]c.Char, llos.PATH_MAX+1) + if result := llos.Getcwd(unsafe.Pointer(&buffer[0]), c.Int(len(buffer))); result == nil { + t.Fatalf("_getcwd failed with errno %d", llos.Errno()) + } else if got := c.GoString(result); got == "" { + t.Fatal("_getcwd returned an empty path") + } +} + +func TestWindowsFileIO(t *testing.T) { + path := c.AllocaCStr(t.TempDir() + `\binding.txt`) + fd := llos.Open(path, llos.O_CREAT|llos.O_TRUNC|llos.O_RDWR, c.Int(0o600)) + if fd < 0 { + t.Fatalf("_open failed with errno %d", llos.Errno()) + } + defer llos.Close(fd) + var stat llos.StatT + if result := llos.Fstat(fd, &stat); result != 0 { + t.Fatalf("_fstat64 returned %d (errno %d)", result, llos.Errno()) + } + if result := llos.Stat(path, &stat); result != 0 { + t.Fatalf("_stat64 returned %d (errno %d)", result, llos.Errno()) + } + + const value = "native UCRT I/O" + if result := llos.Write(fd, unsafe.Pointer(c.GoStringData(value)), c.Uint(len(value))); result != c.Int(len(value)) { + t.Fatalf("_write returned %d, want %d (errno %d)", result, len(value), llos.Errno()) + } + if result := llos.Lseek(fd, 0, 0); result != 0 { + t.Fatalf("_lseeki64 returned %d, want 0 (errno %d)", result, llos.Errno()) + } + + buffer := make([]byte, len(value)) + if result := llos.Read(fd, unsafe.Pointer(&buffer[0]), c.Uint(len(buffer))); result != c.Int(len(buffer)) { + t.Fatalf("_read returned %d, want %d (errno %d)", result, len(buffer), llos.Errno()) + } + if got := string(buffer); got != value { + t.Fatalf("file contents = %q, want %q", got, value) + } +} diff --git a/c/os/stat.go b/c/os/stat.go index a113e95d..8e993dcc 100644 --- a/c/os/stat.go +++ b/c/os/stat.go @@ -1,5 +1,5 @@ -//go:build !darwin -// +build !darwin +//go:build !darwin && !windows +// +build !darwin,!windows package os diff --git a/c/os/stat_windows.go b/c/os/stat_windows.go new file mode 100644 index 00000000..79d71119 --- /dev/null +++ b/c/os/stat_windows.go @@ -0,0 +1,36 @@ +//go:build windows + +/* + * Copyright (c) 2026 The GoPlus Authors (goplus.org). All rights reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package os + +// #include +// typedef struct _stat64 llgo_stat64; +import "C" + +import ( + _ "unsafe" + + "github.com/goplus/lib/c" +) + +// StatT preserves the target UCRT's struct _stat64 size, field offsets, and +// alignment, including its 8-byte alignment on Windows/386. +type StatT = C.llgo_stat64 + +//go:linkname Stat C._stat64 +func Stat(path *c.Char, buf *StatT) c.Int + +//go:linkname Fstat C._fstat64 +func Fstat(fd c.Int, buf *StatT) c.Int diff --git a/c/pthread/pthread.go b/c/pthread/pthread.go index 9b4965cb..2d25ed77 100644 --- a/c/pthread/pthread.go +++ b/c/pthread/pthread.go @@ -1,3 +1,5 @@ +//go:build !windows + /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. * diff --git a/c/pthread/pthread_gc.go b/c/pthread/pthread_gc.go index 59125948..e1c8c119 100644 --- a/c/pthread/pthread_gc.go +++ b/c/pthread/pthread_gc.go @@ -1,5 +1,5 @@ -//go:build !nogc -// +build !nogc +//go:build !nogc && !windows +// +build !nogc,!windows /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. diff --git a/c/pthread/pthread_nogc.go b/c/pthread/pthread_nogc.go index fc062c84..95d4f91d 100644 --- a/c/pthread/pthread_nogc.go +++ b/c/pthread/pthread_nogc.go @@ -1,5 +1,5 @@ -//go:build nogc -// +build nogc +//go:build nogc && !windows +// +build nogc,!windows /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. diff --git a/c/pthread/sync/sync.go b/c/pthread/sync/sync.go index 8c39a30e..1e471849 100644 --- a/c/pthread/sync/sync.go +++ b/c/pthread/sync/sync.go @@ -1,3 +1,5 @@ +//go:build !windows + /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. * diff --git a/c/setjmp/setjmp.go b/c/setjmp/setjmp.go index 17b6d7ac..ef609762 100644 --- a/c/setjmp/setjmp.go +++ b/c/setjmp/setjmp.go @@ -1,3 +1,5 @@ +//go:build !windows + /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. * diff --git a/c/setjmp/setjmp_other.go b/c/setjmp/setjmp_other.go index 913c2454..d87c04ab 100644 --- a/c/setjmp/setjmp_other.go +++ b/c/setjmp/setjmp_other.go @@ -1,4 +1,4 @@ -//go:build !linux +//go:build !linux && !windows package setjmp diff --git a/c/setjmp/setjmp_windows.go b/c/setjmp/setjmp_windows.go new file mode 100644 index 00000000..6a07e42a --- /dev/null +++ b/c/setjmp/setjmp_windows.go @@ -0,0 +1,39 @@ +//go:build windows + +/* + * Copyright (c) 2026 The GoPlus Authors (goplus.org). All rights reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package setjmp + +// #include +import "C" + +import ( + _ "unsafe" + + "github.com/goplus/lib/c" +) + +const LLGoPackage = "decl" + +type JmpBuf = C.jmp_buf + +// Windows has no signal-mask variant of setjmp. These LLGo intrinsics lower +// at the caller, which is required because setjmp cannot safely be hidden in a +// wrapper frame that returns before longjmp restores it. + +//go:linkname Setjmp llgo.setjmp +func Setjmp(env *JmpBuf) c.Int + +//go:linkname Longjmp llgo.longjmp +func Longjmp(env *JmpBuf, val c.Int) diff --git a/c/setjmp/setjmp_windows_llgo_test.go b/c/setjmp/setjmp_windows_llgo_test.go new file mode 100644 index 00000000..d9306967 --- /dev/null +++ b/c/setjmp/setjmp_windows_llgo_test.go @@ -0,0 +1,24 @@ +//go:build llgo && windows + +// Copyright (c) 2026 The GoPlus Authors. Licensed under the Apache License 2.0. + +package setjmp_test + +import ( + "testing" + + "github.com/goplus/lib/c/setjmp" +) + +func TestWindowsSetjmp(t *testing.T) { + var env setjmp.JmpBuf + switch result := setjmp.Setjmp(&env); result { + case 0: + setjmp.Longjmp(&env, 7) + t.Fatal("longjmp returned") + case 7: + return + default: + t.Fatalf("setjmp returned %d, want 0 then 7", result) + } +} diff --git a/c/stdio_darwin.go b/c/stdio_darwin.go index 324403f5..0a4f58ae 100644 --- a/c/stdio_darwin.go +++ b/c/stdio_darwin.go @@ -21,6 +21,8 @@ package c import _ "unsafe" +const LLGoPackage = "decl" + //go:linkname Stdin __stdinp var Stdin FilePtr diff --git a/c/stdio_default.go b/c/stdio_default.go index 5f95fd27..d2889d98 100644 --- a/c/stdio_default.go +++ b/c/stdio_default.go @@ -1,5 +1,5 @@ -//go:build !darwin -// +build !darwin +//go:build !darwin && !windows +// +build !darwin,!windows /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. @@ -21,6 +21,8 @@ package c import _ "unsafe" +const LLGoPackage = "decl" + //go:linkname Stdin stdin var Stdin FilePtr diff --git a/c/stdio_windows.go b/c/stdio_windows.go new file mode 100644 index 00000000..ab02a630 --- /dev/null +++ b/c/stdio_windows.go @@ -0,0 +1,40 @@ +//go:build windows +// +build windows + +/* + * Copyright (c) 2026 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package c + +import _ "unsafe" + +// Standard-stream pointers require package initialization; declaration-only +// and link-only packages intentionally skip init functions. The LLGo Windows +// linker supplies legacy_stdio_definitions.lib for the out-of-line formatted +// stdio symbols shared with the other platforms. +const LLGoPackage = true + +// The Universal CRT exposes standard streams through __acrt_iob_func rather +// than the Unix stdin/stdout/stderr data symbols. + +//go:linkname acrtIobFunc C.__acrt_iob_func +func acrtIobFunc(index Uint) FilePtr + +var ( + Stdin = acrtIobFunc(0) + Stdout = acrtIobFunc(1) + Stderr = acrtIobFunc(2) +) diff --git a/c/stdio_windows_llgo_test.go b/c/stdio_windows_llgo_test.go new file mode 100644 index 00000000..5632cbc5 --- /dev/null +++ b/c/stdio_windows_llgo_test.go @@ -0,0 +1,20 @@ +//go:build llgo && windows + +// Copyright (c) 2026 The GoPlus Authors. Licensed under the Apache License 2.0. + +package c_test + +import ( + "testing" + + "github.com/goplus/lib/c" +) + +func TestWindowsStandardStreams(t *testing.T) { + if c.Stdin == nil || c.Stdout == nil || c.Stderr == nil { + t.Fatalf("standard streams are not initialized: stdin=%p stdout=%p stderr=%p", c.Stdin, c.Stdout, c.Stderr) + } + if result := c.Fputs(c.AllocaCStr("Windows stdio binding test\n"), c.Stdout); result < 0 { + t.Fatalf("fputs returned %d", result) + } +} diff --git a/c/sys/select.go b/c/sys/select.go index 4f7832f3..9449efc9 100644 --- a/c/sys/select.go +++ b/c/sys/select.go @@ -1,3 +1,5 @@ +//go:build !windows + package sys import ( diff --git a/c/syscall/syscall.go b/c/syscall/syscall.go index e9a08e06..86d7a037 100644 --- a/c/syscall/syscall.go +++ b/c/syscall/syscall.go @@ -1,3 +1,5 @@ +//go:build !windows + /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. * diff --git a/c/syscall/syscall_windows.go b/c/syscall/syscall_windows.go new file mode 100644 index 00000000..379f10d5 --- /dev/null +++ b/c/syscall/syscall_windows.go @@ -0,0 +1,31 @@ +//go:build windows + +/* + * Copyright (c) 2026 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package syscall + +const LLGoPackage = "noinit" + +type Errno = uintptr + +// A Signal is a number describing a process signal. +type Signal = int + +const ( + ERROR_FILE_NOT_FOUND Errno = 2 + ERROR_PATH_NOT_FOUND Errno = 3 +) diff --git a/c/syscall/unix/unix.go b/c/syscall/unix/unix.go index ab5144f6..94f92ffc 100644 --- a/c/syscall/unix/unix.go +++ b/c/syscall/unix/unix.go @@ -1,3 +1,5 @@ +//go:build !windows + /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. * diff --git a/c/time/ctime_llgo_test.go b/c/time/ctime_llgo_test.go new file mode 100644 index 00000000..d021f5b2 --- /dev/null +++ b/c/time/ctime_llgo_test.go @@ -0,0 +1,19 @@ +//go:build llgo && !windows + +// Copyright (c) 2026 The GoPlus Authors. Licensed under the Apache License 2.0. + +package time_test + +import ( + "testing" + + "github.com/goplus/lib/c" + lltime "github.com/goplus/lib/c/time" +) + +func TestCtime(t *testing.T) { + now := lltime.Time(nil) + if value := lltime.Ctime(&now); value == nil || c.GoString(value) == "" { + t.Fatal("ctime returned an empty C string") + } +} diff --git a/c/time/time.go b/c/time/time.go index a53a943b..5784e555 100644 --- a/c/time/time.go +++ b/c/time/time.go @@ -1,3 +1,5 @@ +//go:build !windows + /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. * @@ -40,7 +42,7 @@ func Time(timer *TimeT) TimeT func Mktime(timer *Tm) TimeT //go:linkname Ctime C.ctime -func Ctime(timer *TimeT) string +func Ctime(timer *TimeT) *c.Char //go:linkname Difftime C.difftime func Difftime(end, start TimeT) float64 diff --git a/c/time/time_windows.go b/c/time/time_windows.go new file mode 100644 index 00000000..8b08920d --- /dev/null +++ b/c/time/time_windows.go @@ -0,0 +1,69 @@ +//go:build windows + +/* + * Copyright (c) 2026 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package time + +import ( + _ "unsafe" + + "github.com/goplus/lib/c" +) + +const LLGoPackage = "link" + +type TimeT int64 + +// Tm matches the Universal CRT struct tm. The Unix-only tm_gmtoff and tm_zone +// extensions are intentionally absent on Windows. +type Tm struct { + Sec c.Int + Min c.Int + Hour c.Int + Mday c.Int + Mon c.Int + Year c.Int + Wday c.Int + Yday c.Int + Isdst c.Int +} + +//go:linkname Time C._time64 +func Time(timer *TimeT) TimeT + +//go:linkname Mktime C._mktime64 +func Mktime(timer *Tm) TimeT + +//go:linkname Ctime C._ctime64 +func Ctime(timer *TimeT) *c.Char + +//go:linkname Difftime C._difftime64 +func Difftime(end, start TimeT) float64 + +//go:linkname Gmtime C._gmtime64 +func Gmtime(timer *TimeT) *Tm + +//go:linkname Localtime C._localtime64 +func Localtime(timer *TimeT) *Tm + +//go:linkname Strftime C.strftime +func Strftime(buf *c.Char, bufSize uintptr, format *c.Char, timeptr *Tm) uintptr + +type ClockT c.Long + +//go:linkname Clock C.clock +func Clock() ClockT diff --git a/c/time/time_windows_llgo_test.go b/c/time/time_windows_llgo_test.go new file mode 100644 index 00000000..9e78d1bf --- /dev/null +++ b/c/time/time_windows_llgo_test.go @@ -0,0 +1,31 @@ +//go:build llgo && windows + +// Copyright (c) 2026 The GoPlus Authors. Licensed under the Apache License 2.0. + +package time_test + +import ( + "testing" + + "github.com/goplus/lib/c" + lltime "github.com/goplus/lib/c/time" +) + +func TestWindowsTime(t *testing.T) { + now := lltime.Time(nil) + if now <= 0 { + t.Fatalf("_time64 returned %d", now) + } + if got := lltime.Difftime(now, now-1); got != 1 { + t.Fatalf("_difftime64 returned %v, want 1", got) + } + if value := lltime.Gmtime(&now); value == nil { + t.Fatal("_gmtime64 returned nil") + } + if value := lltime.Localtime(&now); value == nil { + t.Fatal("_localtime64 returned nil") + } + if value := lltime.Ctime(&now); value == nil || c.GoString(value) == "" { + t.Fatal("_ctime64 returned an empty C string") + } +} diff --git a/cpp/llvm/_wrap/demangle_windows.cpp b/cpp/llvm/_wrap/demangle_windows.cpp new file mode 100644 index 00000000..11c0e4c7 --- /dev/null +++ b/cpp/llvm/_wrap/demangle_windows.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2026 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if defined(_WIN32) && defined(__clang__) && __clang_major__ < 20 +#define _ALLOW_COMPILER_AND_STL_VERSION_MISMATCH +#endif + +#include + +#include +#include + +static std::string_view llgoLLVMStringView(const char *data, size_t size) { + return size == 0 ? std::string_view() : std::string_view(data, size); +} + +extern "C" { + +char *llgoLLVMItaniumDemangle(const char *data, size_t size, int parseParams) { + return llvm::itaniumDemangle(llgoLLVMStringView(data, size), parseParams != 0); +} + +char *llgoLLVMMicrosoftDemangle(const char *data, size_t size, size_t *nRead, + int *status, int flags) { + return llvm::microsoftDemangle( + llgoLLVMStringView(data, size), nRead, status, + static_cast(flags)); +} + +char *llgoLLVMRustDemangle(const char *data, size_t size) { + return llvm::rustDemangle(llgoLLVMStringView(data, size)); +} + +} // extern "C" diff --git a/cpp/llvm/config_default.go b/cpp/llvm/config_default.go new file mode 100644 index 00000000..acec137d --- /dev/null +++ b/cpp/llvm/config_default.go @@ -0,0 +1,21 @@ +//go:build !windows + +/* + * Copyright (c) 2026 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package llvm + +const LLGoPackage = "link: $(llvm-config --libs) $(llvm-config --ldflags); -lLLVM" diff --git a/cpp/llvm/config_windows.go b/cpp/llvm/config_windows.go new file mode 100644 index 00000000..ac90442c --- /dev/null +++ b/cpp/llvm/config_windows.go @@ -0,0 +1,30 @@ +//go:build windows + +/* + * Copyright (c) 2026 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package llvm + +const ( + // LLVM may itself be built with either the MSVC or libc++ C++ ABI. Compile + // the bridge for the ABI selected by its host clang++, then expose only C + // functions to the LLGo target. This avoids leaking that C++ ABI across the + // package boundary while retaining the target's native C ABI. + LLGoFiles = "$(pkg-config --cflags llvm-19) -std=c++17 --target=$(llvm-config --host-target): _wrap/demangle_windows.cpp" + // The llvm-19 package preserves LLVM's library order and required Windows + // system libraries. + LLGoPackage = "link: $(pkg-config --libs llvm-19)" +) diff --git a/cpp/llvm/demangle.go b/cpp/llvm/demangle.go index 85902b5f..1e399101 100644 --- a/cpp/llvm/demangle.go +++ b/cpp/llvm/demangle.go @@ -1,3 +1,5 @@ +//go:build !windows + /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. * @@ -33,19 +35,6 @@ import ( //go:linkname ItaniumDemangle C._ZN4llvm15itaniumDemangleENSt3__117basic_string_viewIcNS0_11char_traitsIcEEEEb func ItaniumDemangle(mangledName StringView, parseParams bool) *c.Char -/* - enum MSDemangleFlags { - MSDF_None = 0, - MSDF_DumpBackrefs = 1 << 0, - MSDF_NoAccessSpecifier = 1 << 1, - MSDF_NoCallingConvention = 1 << 2, - MSDF_NoReturnType = 1 << 3, - MSDF_NoMemberType = 1 << 4, - MSDF_NoVariableType = 1 << 5, - }; -*/ -type MSDemangleFlags c.Int - // Demangles the Microsoft symbol pointed at by mangled_name and returns it. // Returns a pointer to the start of a null-terminated demangled string on // success, or nullptr on error. diff --git a/cpp/llvm/demangle_llgo_test.go b/cpp/llvm/demangle_llgo_test.go new file mode 100644 index 00000000..ae6fe3c2 --- /dev/null +++ b/cpp/llvm/demangle_llgo_test.go @@ -0,0 +1,58 @@ +//go:build llgo + +/* + * Copyright (c) 2026 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package llvm_test + +import ( + "strings" + "testing" + "unsafe" + + "github.com/goplus/lib/c" + "github.com/goplus/lib/cpp/llvm" +) + +func demangledString(t *testing.T, value *c.Char) string { + t.Helper() + if value == nil { + t.Fatal("demangler returned nil") + } + defer c.Free(unsafe.Pointer(value)) + return c.GoString(value) +} + +func TestItaniumDemangle(t *testing.T) { + if got := demangledString(t, llvm.ItaniumDemangle("_Z3foov", true)); got != "foo()" { + t.Fatalf("ItaniumDemangle = %q, want %q", got, "foo()") + } +} + +func TestMicrosoftDemangle(t *testing.T) { + var nRead uintptr + var status c.Int + got := demangledString(t, llvm.MicrosoftDemangle("?foo@@YAHH@Z", &nRead, &status, 0)) + if status != 0 || nRead == 0 || !strings.Contains(got, "foo") { + t.Fatalf("MicrosoftDemangle = %q, nRead %d, status %d", got, nRead, status) + } +} + +func TestRustDemangle(t *testing.T) { + if got := demangledString(t, llvm.RustDemangle("_RNvC6_123foo3bar")); got != "123foo::bar" { + t.Fatalf("RustDemangle = %q, want %q", got, "123foo::bar") + } +} diff --git a/cpp/llvm/demangle_types.go b/cpp/llvm/demangle_types.go new file mode 100644 index 00000000..c74ac184 --- /dev/null +++ b/cpp/llvm/demangle_types.go @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2026 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package llvm + +import "github.com/goplus/lib/c" + +/* + enum MSDemangleFlags { + MSDF_None = 0, + MSDF_DumpBackrefs = 1 << 0, + MSDF_NoAccessSpecifier = 1 << 1, + MSDF_NoCallingConvention = 1 << 2, + MSDF_NoReturnType = 1 << 3, + MSDF_NoMemberType = 1 << 4, + MSDF_NoVariableType = 1 << 5, + }; +*/ +type MSDemangleFlags c.Int diff --git a/cpp/llvm/demangle_windows.go b/cpp/llvm/demangle_windows.go new file mode 100644 index 00000000..ebbd51e9 --- /dev/null +++ b/cpp/llvm/demangle_windows.go @@ -0,0 +1,54 @@ +//go:build windows + +/* + * Copyright (c) 2026 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package llvm + +import ( + _ "unsafe" + + "github.com/goplus/lib/c" +) + +// LLVM's official Windows binaries use the MSVC standard library, whose C++ +// symbol names differ from the libc++ names used on Unix. Keep those names +// behind the C ABI implemented by demangle_windows.cpp. + +//go:linkname itaniumDemangle C.llgoLLVMItaniumDemangle +func itaniumDemangle(data *c.Char, size uintptr, parseParams c.Int) *c.Char + +func ItaniumDemangle(mangledName StringView, parseParams bool) *c.Char { + parse := c.Int(0) + if parseParams { + parse = 1 + } + return itaniumDemangle(c.GoStringData(mangledName), uintptr(len(mangledName)), parse) +} + +//go:linkname microsoftDemangle C.llgoLLVMMicrosoftDemangle +func microsoftDemangle(data *c.Char, size uintptr, nRead *uintptr, status *c.Int, flags MSDemangleFlags) *c.Char + +func MicrosoftDemangle(mangledName StringView, nRead *uintptr, status *c.Int, flags MSDemangleFlags) *c.Char { + return microsoftDemangle(c.GoStringData(mangledName), uintptr(len(mangledName)), nRead, status, flags) +} + +//go:linkname rustDemangle C.llgoLLVMRustDemangle +func rustDemangle(data *c.Char, size uintptr) *c.Char + +func RustDemangle(mangledName StringView) *c.Char { + return rustDemangle(c.GoStringData(mangledName), uintptr(len(mangledName))) +} diff --git a/cpp/llvm/llvm.go b/cpp/llvm/llvm.go index 3130750b..100a1259 100644 --- a/cpp/llvm/llvm.go +++ b/cpp/llvm/llvm.go @@ -18,10 +18,6 @@ package llvm // ----------------------------------------------------------------------------- -const ( - LLGoPackage = "link: $(llvm-config --libs) $(llvm-config --ldflags); -lLLVM" -) - // StringView represents a C++ std::string_view object. type StringView = string diff --git a/cpp/std/_wrap/string.cpp b/cpp/std/_wrap/string.cpp index ba17a915..21879395 100644 --- a/cpp/std/_wrap/string.cpp +++ b/cpp/std/_wrap/string.cpp @@ -1,6 +1,21 @@ +#if defined(_WIN32) && defined(__clang__) && __clang_major__ < 20 +// The current Visual Studio STL rejects older Clang releases by default. +// LLGo intentionally supports LLVM 19, whose MSVC ABI is sufficient for this +// wrapper; use the STL's documented escape hatch instead of changing the +// compiler or C++ ABI selected by the caller. +#define _ALLOW_COMPILER_AND_STL_VERSION_MISMATCH +#endif + #include #include +#if defined(_WIN32) && defined(_MSC_VER) +static_assert(sizeof(std::string) == 16 + 2 * sizeof(void *), + "unexpected MSVC std::string layout"); +static_assert(alignof(std::string) == alignof(void *), + "unexpected MSVC std::string alignment"); +#endif + extern "C" { // ----------------------------------------------------------------------------- diff --git a/cpp/std/config_default.go b/cpp/std/config_default.go new file mode 100644 index 00000000..dfcffd9a --- /dev/null +++ b/cpp/std/config_default.go @@ -0,0 +1,21 @@ +//go:build !windows + +/* + * Copyright (c) 2026 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package std + +const LLGoPackage = "link: c++" diff --git a/cpp/std/config_windows.go b/cpp/std/config_windows.go new file mode 100644 index 00000000..174953b6 --- /dev/null +++ b/cpp/std/config_windows.go @@ -0,0 +1,23 @@ +//go:build windows + +/* + * Copyright (c) 2026 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package std + +// clang++ selects the MSVC C++ runtime through its target defaults. Passing the +// Unix "c++" library name would instead ask lld-link for a nonexistent c++.lib. +const LLGoPackage = "link" diff --git a/cpp/std/std.go b/cpp/std/std.go index 9de32bff..0f9a46ba 100644 --- a/cpp/std/std.go +++ b/cpp/std/std.go @@ -19,8 +19,7 @@ package std // ----------------------------------------------------------------------------- const ( - LLGoFiles = "_wrap/string.cpp" - LLGoPackage = "link: c++" + LLGoFiles = "_wrap/string.cpp" ) // ----------------------------------------------------------------------------- diff --git a/cpp/std/string.go b/cpp/std/string.go index 1cf59880..9a67e0bb 100644 --- a/cpp/std/string.go +++ b/cpp/std/string.go @@ -29,11 +29,6 @@ type StringView = string // ----------------------------------------------------------------------------- -// String represents a C++ std::string object. -type String struct { - Unused [3 * unsafe.Sizeof(0)]byte -} - // llgo:link (*String).InitEmpty C.stdStringInitEmpty func (s *String) InitEmpty() {} diff --git a/cpp/std/string_llgo_test.go b/cpp/std/string_llgo_test.go new file mode 100644 index 00000000..c2f7d1dc --- /dev/null +++ b/cpp/std/string_llgo_test.go @@ -0,0 +1,31 @@ +//go:build llgo + +// Copyright (c) 2026 The GoPlus Authors. Licensed under the Apache License 2.0. + +package std_test + +import ( + "runtime" + "testing" + "unsafe" + + "github.com/goplus/lib/cpp/std" +) + +func TestStringRoundTrip(t *testing.T) { + if runtime.GOOS == "windows" { + var stringValue std.String + if got, want := unsafe.Sizeof(stringValue), uintptr(16)+2*unsafe.Sizeof(uintptr(0)); got != want { + t.Fatalf("sizeof(std::string) = %d, want %d", got, want) + } + if got, want := unsafe.Alignof(stringValue), unsafe.Alignof(uintptr(0)); got != want { + t.Fatalf("alignof(std::string) = %d, want %d", got, want) + } + } + + const value = "LLGo C++ string" + stringValue := std.NewString(value) + if got := stringValue.Str(); got != value { + t.Fatalf("String.Str() = %q, want %q", got, value) + } +} diff --git a/cpp/std/string_type_default.go b/cpp/std/string_type_default.go new file mode 100644 index 00000000..0959b2ac --- /dev/null +++ b/cpp/std/string_type_default.go @@ -0,0 +1,12 @@ +//go:build !windows + +// Copyright (c) 2026 The GoPlus Authors. Licensed under the Apache License 2.0. + +package std + +import "unsafe" + +// String represents a libc++ std::string object. +type String struct { + Unused [3 * unsafe.Sizeof(0)]byte +} diff --git a/cpp/std/string_type_windows.go b/cpp/std/string_type_windows.go new file mode 100644 index 00000000..37ce44f5 --- /dev/null +++ b/cpp/std/string_type_windows.go @@ -0,0 +1,12 @@ +//go:build windows + +// Copyright (c) 2026 The GoPlus Authors. Licensed under the Apache License 2.0. + +package std + +// String reserves the release-mode MSVC std::string layout: a 16-byte small +// string buffer followed by size and capacity fields. +type String struct { + storage [16]byte + words [2]uintptr +}