Skip to content

Commit 1977c28

Browse files
quadcpusolardiz
authored andcommitted
Add MSVC build script, fix build for MSVC and macOS, add GitHub Actions
1 parent 0126753 commit 1977c28

8 files changed

Lines changed: 360 additions & 15 deletions

File tree

.github/workflows/build.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: build-and-test
2+
3+
on:
4+
push:
5+
pull_request:
6+
workflow_dispatch:
7+
8+
jobs:
9+
build-test:
10+
name: ${{ matrix.os }}
11+
runs-on: ${{ matrix.os }}
12+
strategy:
13+
fail-fast: false
14+
matrix:
15+
os: [ubuntu-latest, windows-latest, macos-latest]
16+
17+
steps:
18+
- name: Checkout
19+
uses: actions/checkout@v6
20+
21+
- name: Build and test (Linux)
22+
if: runner.os == 'Linux'
23+
run: |
24+
make check
25+
make benchmark
26+
./benchmark
27+
28+
# Apple's clang doesn't accept -march=native on arm64 and doesn't bundle
29+
# OpenMP, so override CFLAGS/LDFLAGS and skip benchmark.
30+
- name: Build and test (macOS)
31+
if: runner.os == 'macOS'
32+
run: make check CFLAGS="-Wall -O2 -fomit-frame-pointer" LDFLAGS=""
33+
34+
- name: Build and test (Windows)
35+
if: runner.os == 'Windows'
36+
shell: powershell
37+
run: |
38+
.\build.ps1 -Target check
39+
.\build.ps1 -Target benchmark

.gitignore

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Build artifacts (Unix Makefile)
2+
*.o
3+
/tests
4+
/phc-test
5+
/initrom
6+
/userom
7+
8+
# Build artifacts (MSVC / build.ps1)
9+
*.obj
10+
*.exe
11+
*.pdb
12+
*.ilk
13+
14+
# Test output
15+
TESTS-OUT
16+
PHC-TEST-OUT

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ all: $(PROJ)
3939
check: tests
4040
@echo 'Running tests'
4141
@time ./tests | tee TESTS-OUT
42-
@diff -U0 TESTS-OK TESTS-OUT && echo PASSED || echo FAILED
42+
@diff -U0 TESTS-OK TESTS-OUT && echo PASSED || { echo FAILED; exit 1; }
4343

4444
ref:
4545
$(MAKE) $(PROJ) OBJS_CORE=yespower-ref.o

README

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,21 @@ We do most of our testing on Linux systems with gcc. The supplied
116116
Makefile assumes that you use gcc.
117117

118118

119+
On Windows.
120+
121+
A PowerShell script, build.ps1, is provided to build yespower with the
122+
MSVC toolchain. It requires Visual Studio Community 2026 with the
123+
"Desktop development with C++" workload installed (this also provides
124+
vswhere.exe, which the script uses to locate the compiler automatically).
125+
From a PowerShell prompt in this directory, run:
126+
127+
powershell -ExecutionPolicy Bypass -File .\build.ps1 -Target check
128+
129+
This builds and runs tests.exe and prints "PASSED" on success. Other
130+
targets mirror the Makefile: "all" (default), "tests", "benchmark",
131+
"ref", "check-ref", and "clean".
132+
133+
119134
Alternate code versions and make targets.
120135

121136
Two implementations of yespower are included: reference and optimized.

benchmark.c

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,63 @@
2121
#include <stdio.h>
2222
#include <stdlib.h> /* for atoi() */
2323
#include <string.h>
24-
#include <unistd.h>
2524
#include <time.h>
25+
#ifndef _MSC_VER
26+
#include <unistd.h>
2627
#include <sys/times.h>
2728
#include <sched.h>
29+
#else
30+
/*
31+
* MSVC lacks the POSIX <unistd.h>, <sys/times.h> and <sched.h> interfaces used
32+
* below. Provide a minimal shim implemented on top of the Win32 API so that
33+
* benchmark.c builds and the single-threaded benchmark works. The optional
34+
* OpenMP multi-threaded section is only compiled when _OPENMP is defined, which
35+
* build.ps1 does not enable for MSVC.
36+
*/
37+
#include <windows.h>
38+
39+
struct tms {
40+
clock_t tms_utime;
41+
clock_t tms_stime;
42+
clock_t tms_cutime;
43+
clock_t tms_cstime;
44+
};
45+
46+
#define _SC_CLK_TCK 1
47+
48+
/* Report a 1 ms tick so wall- and CPU-times share the same units. */
49+
static long sysconf(int name)
50+
{
51+
(void)name;
52+
return 1000;
53+
}
54+
55+
/* 100 ns FILETIME units -> 1 ms ticks. */
56+
static clock_t filetime_to_ticks(const FILETIME *ft)
57+
{
58+
ULARGE_INTEGER t;
59+
t.LowPart = ft->dwLowDateTime;
60+
t.HighPart = ft->dwHighDateTime;
61+
return (clock_t)(t.QuadPart / 10000);
62+
}
63+
64+
/* Wall-clock as return value (1 ms ticks), process CPU times in *buf. */
65+
static clock_t times(struct tms *buf)
66+
{
67+
FILETIME creation, exit, kernel, user;
68+
LARGE_INTEGER freq, now;
69+
70+
GetProcessTimes(GetCurrentProcess(), &creation, &exit, &kernel, &user);
71+
buf->tms_utime = filetime_to_ticks(&user);
72+
buf->tms_stime = filetime_to_ticks(&kernel);
73+
buf->tms_cutime = 0;
74+
buf->tms_cstime = 0;
75+
76+
QueryPerformanceFrequency(&freq);
77+
QueryPerformanceCounter(&now);
78+
return (clock_t)(now.QuadPart * 1000 / freq.QuadPart);
79+
}
80+
#endif
2881

2982
#include "yespower.h"
3083

@@ -138,6 +191,7 @@ int main(int argc, const char * const *argv)
138191
count * clk_tck / (end_v - start_v),
139192
count, (double)(end - start) / clk_tck);
140193

194+
#ifdef _OPENMP
141195
for (i = 0; i < nsave; i++) {
142196
unsigned int j;
143197
for (j = i + 1; j < nsave; j++) {
@@ -149,7 +203,6 @@ int main(int argc, const char * const *argv)
149203
}
150204
}
151205

152-
#ifdef _OPENMP
153206
unsigned int nt = omp_get_max_threads();
154207

155208
printf("Benchmarking %u thread%s ...\n",
@@ -171,6 +224,7 @@ int main(int argc, const char * const *argv)
171224

172225
unsigned long long count1 = count, count_restart = 0;
173226

227+
#ifdef SCHED_RR
174228
if (!geteuid()) {
175229
puts("Running as root, so trying to set SCHED_RR");
176230
#pragma omp parallel
@@ -180,6 +234,7 @@ int main(int argc, const char * const *argv)
180234
perror("sched_setscheduler");
181235
}
182236
}
237+
#endif
183238

184239
start = times(&start_tms);
185240

build.ps1

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
#requires -Version 5.1
2+
<#
3+
.COPYRIGHT
4+
Copyright 2013-2026 Alexander Peslyak
5+
Copyright 2026 CPUchain
6+
All rights reserved.
7+
8+
Redistribution and use in source and binary forms, with or without
9+
modification, are permitted.
10+
11+
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
12+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
13+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
14+
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
15+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
16+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
17+
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
18+
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
19+
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
20+
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
21+
SUCH DAMAGE.
22+
23+
.SYNOPSIS
24+
Build yespower with the MSVC toolchain (port of the supplied GNU Makefile).
25+
26+
.DESCRIPTION
27+
Locates a Visual Studio / Build Tools installation with the C++ compiler
28+
using vswhere.exe, imports its x64 environment, and compiles the yespower
29+
"tests" and "benchmark" programs with cl.exe / link.exe.
30+
31+
Targets (mirroring the Makefile):
32+
build.ps1 # build tests.exe and benchmark.exe (optimized)
33+
build.ps1 -Target check # build and run tests.exe, diff against TESTS-OK
34+
build.ps1 -Target benchmark # build and run benchmark.exe
35+
build.ps1 -Target ref # build using the reference implementation
36+
build.ps1 -Target check-ref
37+
build.ps1 -Target clean # remove build artifacts
38+
39+
.NOTES
40+
Requires Visual Studio 2026 (Community is fine) with the
41+
"Desktop development with C++" workload installed. See the README section
42+
"How to test yespower for proper operation." for details.
43+
#>
44+
[CmdletBinding()]
45+
param(
46+
[ValidateSet('all', 'tests', 'benchmark', 'check', 'ref', 'check-ref', 'clean')]
47+
[string]$Target = 'all'
48+
)
49+
50+
$ErrorActionPreference = 'Stop'
51+
Set-Location -LiteralPath $PSScriptRoot
52+
53+
# --- Toolchain detection via vswhere.exe ------------------------------------
54+
55+
function Find-VsWhere {
56+
$candidates = @(
57+
(Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe'),
58+
(Join-Path $env:ProgramFiles 'Microsoft Visual Studio\Installer\vswhere.exe')
59+
)
60+
foreach ($c in $candidates) {
61+
if ($c -and (Test-Path -LiteralPath $c)) { return $c }
62+
}
63+
$cmd = Get-Command vswhere.exe -ErrorAction SilentlyContinue
64+
if ($cmd) { return $cmd.Source }
65+
throw "vswhere.exe not found. Install Visual Studio 2026 (it ships vswhere)."
66+
}
67+
68+
function Get-VcVarsPath {
69+
$vswhere = Find-VsWhere
70+
# Require the x64/x86 C++ compiler toolset.
71+
$installPath = & $vswhere -latest -products * `
72+
-requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 `
73+
-property installationPath
74+
if (-not $installPath) {
75+
throw "No Visual Studio installation with the C++ toolset was found. " +
76+
"Install the 'Desktop development with C++' workload."
77+
}
78+
$vcvars = Join-Path $installPath 'VC\Auxiliary\Build\vcvars64.bat'
79+
if (-not (Test-Path -LiteralPath $vcvars)) {
80+
throw "vcvars64.bat not found under '$installPath'."
81+
}
82+
return $vcvars
83+
}
84+
85+
# Import the MSVC x64 environment (PATH, INCLUDE, LIB, ...) into this session.
86+
function Import-VcEnvironment {
87+
$vcvars = Get-VcVarsPath
88+
Write-Host "Using MSVC environment: $vcvars"
89+
$marker = '___VCVARS_ENV___'
90+
# vcvars64.bat may emit benign stderr noise; don't let it abort the script.
91+
$lines = & cmd /c "`"$vcvars`" 2>nul && echo $marker && set"
92+
$seen = $false
93+
foreach ($line in $lines) {
94+
if (-not $seen) {
95+
if ($line.Trim() -eq $marker) { $seen = $true }
96+
continue
97+
}
98+
if ($line -match '^([^=]+)=(.*)$') {
99+
Set-Item -Path ("Env:" + $matches[1]) -Value $matches[2]
100+
}
101+
}
102+
if (-not $seen) { throw "Failed to import the MSVC environment." }
103+
}
104+
105+
# --- Compiler / linker settings (port of Makefile CFLAGS/LDFLAGS) -----------
106+
107+
# /O2 -> -O2
108+
# /D__SSE2__ -> enable the SSE2 intrinsic code path (MSVC x64 supports
109+
# <emmintrin.h>; this path is free of GCC inline asm).
110+
# /DUSE_OPENMP -> intentionally NOT set: benchmark.c's OpenMP section relies
111+
# on POSIX-only APIs and VLAs that MSVC cannot build.
112+
$CFLAGS = @('/nologo', '/O2', '/MT', '/D__SSE2__', '/wd4146', '/wd4244')
113+
114+
$OBJS_COMMON = @('sha256.obj')
115+
$OBJS_CORE_OPT = 'yespower-opt.obj'
116+
$OBJS_CORE_REF = 'yespower-ref.obj'
117+
118+
function Invoke-Tool {
119+
param([string]$Exe, [string[]]$Arguments)
120+
Write-Host ">> $Exe $($Arguments -join ' ')"
121+
# Send tool output to the host so it doesn't pollute function return values.
122+
& $Exe @Arguments | Out-Host
123+
if ($LASTEXITCODE -ne 0) {
124+
throw "$Exe failed with exit code $LASTEXITCODE"
125+
}
126+
}
127+
128+
function Compile-One {
129+
param([string]$Source, [string[]]$ExtraFlags = @())
130+
$obj = [System.IO.Path]::GetFileNameWithoutExtension($Source) + '.obj'
131+
Invoke-Tool 'cl' (@('/c') + $CFLAGS + $ExtraFlags + @("/Fo$obj", $Source))
132+
return $obj
133+
}
134+
135+
function Link-Exe {
136+
param([string]$Out, [string[]]$Objs)
137+
Invoke-Tool 'link' (@('/nologo', "/OUT:$Out") + $Objs)
138+
}
139+
140+
function Build-Tests {
141+
param([string]$Core = $OBJS_CORE_OPT)
142+
$coreSrc = if ($Core -eq $OBJS_CORE_REF) { 'yespower-ref.c' } else { 'yespower-opt.c' }
143+
$objs = @()
144+
$objs += Compile-One $coreSrc
145+
$objs += Compile-One 'sha256.c'
146+
$objs += Compile-One 'tests.c'
147+
Link-Exe 'tests.exe' $objs
148+
Write-Host "Built tests.exe"
149+
}
150+
151+
function Build-Benchmark {
152+
param([string]$Core = $OBJS_CORE_OPT)
153+
$coreSrc = if ($Core -eq $OBJS_CORE_REF) { 'yespower-ref.c' } else { 'yespower-opt.c' }
154+
$objs = @()
155+
$objs += Compile-One $coreSrc
156+
$objs += Compile-One 'sha256.c'
157+
$objs += Compile-One 'benchmark.c'
158+
Link-Exe 'benchmark.exe' $objs
159+
Write-Host "Built benchmark.exe"
160+
}
161+
162+
function Invoke-Benchmark {
163+
param([string]$Core = $OBJS_CORE_OPT)
164+
Build-Benchmark -Core $Core
165+
Write-Host 'Running benchmark'
166+
& .\benchmark.exe | Out-Host
167+
if ($LASTEXITCODE -ne 0) { throw "benchmark.exe failed with exit code $LASTEXITCODE" }
168+
}
169+
170+
function Invoke-Check {
171+
param([string]$Core = $OBJS_CORE_OPT)
172+
Build-Tests -Core $Core
173+
Write-Host 'Running tests'
174+
& .\tests.exe | Out-File -Encoding ascii -FilePath 'TESTS-OUT'
175+
if ($LASTEXITCODE -ne 0) { throw "tests.exe failed with exit code $LASTEXITCODE" }
176+
# Compare against the reference output, ignoring CRLF/LF differences.
177+
$expected = (Get-Content -Raw 'TESTS-OK') -replace "`r`n", "`n"
178+
$actual = (Get-Content -Raw 'TESTS-OUT') -replace "`r`n", "`n"
179+
if ($expected.TrimEnd("`n") -eq $actual.TrimEnd("`n")) {
180+
Write-Host 'PASSED' -ForegroundColor Green
181+
} else {
182+
Write-Host 'FAILED' -ForegroundColor Red
183+
Compare-Object ($expected -split "`n") ($actual -split "`n") |
184+
Format-Table -AutoSize | Out-String | Write-Host
185+
exit 1
186+
}
187+
}
188+
189+
function Invoke-Clean {
190+
$patterns = @('*.obj', 'tests.exe', 'benchmark.exe', 'TESTS-OUT',
191+
'*.ilk', '*.pdb', '_probe*', '_run.ps1', '_vcout.txt')
192+
foreach ($p in $patterns) {
193+
Get-ChildItem -LiteralPath $PSScriptRoot -Filter $p -ErrorAction SilentlyContinue |
194+
Remove-Item -Force -ErrorAction SilentlyContinue
195+
}
196+
Write-Host 'Cleaned build artifacts.'
197+
}
198+
199+
# --- Dispatch ---------------------------------------------------------------
200+
201+
switch ($Target) {
202+
'clean' { Invoke-Clean; break }
203+
default {
204+
Import-VcEnvironment
205+
switch ($Target) {
206+
'all' { Build-Tests; Build-Benchmark }
207+
'tests' { Build-Tests }
208+
'benchmark' { Invoke-Benchmark }
209+
'check' { Invoke-Check }
210+
'ref' { Build-Tests -Core $OBJS_CORE_REF; Build-Benchmark -Core $OBJS_CORE_REF }
211+
'check-ref' { Invoke-Check -Core $OBJS_CORE_REF }
212+
}
213+
}
214+
}

0 commit comments

Comments
 (0)