Skip to content

Commit 4ff14be

Browse files
committed
Add MSVC build scripts & fix for MSVC
1 parent 62ee1f0 commit 4ff14be

7 files changed

Lines changed: 387 additions & 12 deletions

File tree

.github/workflows/build.yml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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+
# Linux: the supplied Makefile works as-is (gcc, -lrt, OpenMP).
22+
- name: Build and test (Linux)
23+
if: runner.os == 'Linux'
24+
run: |
25+
make tests phc-test
26+
./tests > TESTS-OUT
27+
diff -u TESTS-OK TESTS-OUT
28+
echo PASSED
29+
./phc-test > PHC-TEST-OUT
30+
sha256sum -c PHC-TEST-OK-SHA256
31+
echo "PHC PASSED"
32+
33+
# macOS: Apple's linker has no -lrt and rejects the Makefile's -s flag,
34+
# so build with overridden flags. initrom/userom are skipped (they need
35+
# SysV shared memory).
36+
- name: Build and test (macOS)
37+
if: runner.os == 'macOS'
38+
run: |
39+
make tests phc-test \
40+
CFLAGS="-Wall -O2 -fomit-frame-pointer -DSKIP_MEMZERO" \
41+
OMPFLAGS_MAYBE="" LDFLAGS=""
42+
./tests > TESTS-OUT
43+
diff -u TESTS-OK TESTS-OUT
44+
echo PASSED
45+
./phc-test > PHC-TEST-OUT
46+
shasum -a 256 -c PHC-TEST-OK-SHA256
47+
echo "PHC PASSED"
48+
49+
# Windows: build and test with the MSVC toolchain via build.ps1, which
50+
# builds tests.exe and phc-test.exe, runs them, and verifies output
51+
# against TESTS-OK / PHC-TEST-OK-SHA256, exiting non-zero on mismatch.
52+
# shell: powershell uses Windows PowerShell 5.1 (shipped with Windows)
53+
# rather than PowerShell 7, verifying 5.1 compatibility.
54+
- name: Build and test (Windows)
55+
if: runner.os == 'Windows'
56+
shell: powershell
57+
run: .\build.ps1 -Target check

.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

README

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,22 @@ of output.
124124
We do most of our testing on Linux systems with gcc. The supplied
125125
Makefile assumes that you use gcc.
126126

127+
On Windows.
128+
129+
A PowerShell script, build.ps1, is provided to build yescrypt with the
130+
MSVC toolchain. It requires Visual Studio Community 2026 with the
131+
"Desktop development with C++" workload installed (this also provides
132+
vswhere.exe, which the script uses to locate the compiler automatically).
133+
From a PowerShell prompt in this directory, run:
134+
135+
powershell -ExecutionPolicy Bypass -File .\build.ps1 -Target check
136+
137+
This builds and runs tests.exe and phc-test.exe, printing "PASSED" and
138+
"PHC PASSED" on success. Other targets mirror the Makefile: "all"
139+
(default), "tests", "phc-test", "ref", "check-ref", and "clean". The
140+
"initrom" and "userom" programs are not built on Windows, as they rely on
141+
POSIX shared memory and mmap.
142+
127143

128144
ROM in SysV shared memory demo and benchmark.
129145

build.ps1

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
#requires -Version 5.1
2+
<#
3+
.SYNOPSIS
4+
Build yescrypt with the MSVC toolchain (port of the supplied GNU Makefile).
5+
6+
.DESCRIPTION
7+
Locates a Visual Studio / Build Tools installation with the C++ compiler
8+
using vswhere.exe, imports its x64 environment, and compiles the yescrypt
9+
"tests" and "phc-test" programs with cl.exe / link.exe.
10+
11+
The Makefile's "initrom" and "userom" programs are intentionally NOT built:
12+
they rely on POSIX shared memory (sys/shm.h) and mmap, which have no MSVC
13+
equivalent. "tests" and "phc-test" are what "make check" exercises.
14+
15+
Targets (mirroring the Makefile):
16+
build.ps1 # build tests.exe and phc-test.exe (optimized)
17+
build.ps1 -Target check # build and run both, verify against known-good
18+
build.ps1 -Target ref # build using the reference implementation
19+
build.ps1 -Target check-ref
20+
build.ps1 -Target clean # remove build artifacts
21+
22+
.NOTES
23+
Requires Visual Studio 2026 (Community is fine) with the
24+
"Desktop development with C++" workload installed. See the README section
25+
"How to test yescrypt for proper operation." for details.
26+
#>
27+
[CmdletBinding()]
28+
param(
29+
[ValidateSet('all', 'tests', 'phc-test', 'check', 'ref', 'check-ref', 'clean')]
30+
[string]$Target = 'all'
31+
)
32+
33+
$ErrorActionPreference = 'Stop'
34+
Set-Location -LiteralPath $PSScriptRoot
35+
36+
# --- Toolchain detection via vswhere.exe ------------------------------------
37+
38+
function Find-VsWhere {
39+
$candidates = @(
40+
(Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe'),
41+
(Join-Path $env:ProgramFiles 'Microsoft Visual Studio\Installer\vswhere.exe')
42+
)
43+
foreach ($c in $candidates) {
44+
if ($c -and (Test-Path -LiteralPath $c)) { return $c }
45+
}
46+
$cmd = Get-Command vswhere.exe -ErrorAction SilentlyContinue
47+
if ($cmd) { return $cmd.Source }
48+
throw "vswhere.exe not found. Install Visual Studio 2026 (it ships vswhere)."
49+
}
50+
51+
function Get-VcVarsPath {
52+
$vswhere = Find-VsWhere
53+
# Require the x64/x86 C++ compiler toolset.
54+
$installPath = & $vswhere -latest -products * `
55+
-requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 `
56+
-property installationPath
57+
if (-not $installPath) {
58+
throw "No Visual Studio installation with the C++ toolset was found. " +
59+
"Install the 'Desktop development with C++' workload."
60+
}
61+
$vcvars = Join-Path $installPath 'VC\Auxiliary\Build\vcvars64.bat'
62+
if (-not (Test-Path -LiteralPath $vcvars)) {
63+
throw "vcvars64.bat not found under '$installPath'."
64+
}
65+
return $vcvars
66+
}
67+
68+
# Import the MSVC x64 environment (PATH, INCLUDE, LIB, ...) into this session.
69+
function Import-VcEnvironment {
70+
$vcvars = Get-VcVarsPath
71+
Write-Host "Using MSVC environment: $vcvars"
72+
$marker = '___VCVARS_ENV___'
73+
# vcvars64.bat may emit benign stderr noise; don't let it abort the script.
74+
$lines = & cmd /c "`"$vcvars`" 2>nul && echo $marker && set"
75+
$seen = $false
76+
foreach ($line in $lines) {
77+
if (-not $seen) {
78+
if ($line.Trim() -eq $marker) { $seen = $true }
79+
continue
80+
}
81+
if ($line -match '^([^=]+)=(.*)$') {
82+
Set-Item -Path ("Env:" + $matches[1]) -Value $matches[2]
83+
}
84+
}
85+
if (-not $seen) { throw "Failed to import the MSVC environment." }
86+
}
87+
88+
# --- Compiler / linker settings (port of Makefile CFLAGS) -------------------
89+
90+
# /O2 -> -O2
91+
# /D__SSE2__ -> enable the SSE2 intrinsic code path (MSVC x64 supports
92+
# <emmintrin.h>; this path is free of GCC inline asm).
93+
# /DSKIP_MEMZERO -> matches the Makefile's -DSKIP_MEMZERO.
94+
# OpenMP is intentionally not enabled: it is only used by userom, which is not
95+
# built here.
96+
$CFLAGS = @('/nologo', '/O2', '/MT', '/D__SSE2__', '/DSKIP_MEMZERO', '/wd4146', '/wd4244')
97+
98+
$OBJS_COMMON = @('yescrypt-common.obj', 'sha256.obj', 'insecure_memzero.obj')
99+
$OBJS_CORE_OPT = 'yescrypt-opt.obj'
100+
$OBJS_CORE_REF = 'yescrypt-ref.obj'
101+
102+
function Invoke-Tool {
103+
param([string]$Exe, [string[]]$Arguments)
104+
Write-Host ">> $Exe $($Arguments -join ' ')"
105+
# Send tool output to the host so it doesn't pollute function return values.
106+
& $Exe @Arguments | Out-Host
107+
if ($LASTEXITCODE -ne 0) {
108+
throw "$Exe failed with exit code $LASTEXITCODE"
109+
}
110+
}
111+
112+
function Compile-One {
113+
param([string]$Source, [string]$Obj, [string[]]$ExtraFlags = @())
114+
if (-not $Obj) {
115+
$Obj = [System.IO.Path]::GetFileNameWithoutExtension($Source) + '.obj'
116+
}
117+
Invoke-Tool 'cl' (@('/c') + $CFLAGS + $ExtraFlags + @("/Fo$Obj", $Source))
118+
return $Obj
119+
}
120+
121+
function Link-Exe {
122+
param([string]$Out, [string[]]$Objs)
123+
Invoke-Tool 'link' (@('/nologo', "/OUT:$Out") + $Objs)
124+
}
125+
126+
# Compile the core + common objects shared by every program.
127+
function Build-Common {
128+
param([string]$Core = $OBJS_CORE_OPT)
129+
$coreSrc = if ($Core -eq $OBJS_CORE_REF) { 'yescrypt-ref.c' } else { 'yescrypt-opt.c' }
130+
Compile-One $coreSrc $Core | Out-Null
131+
Compile-One 'yescrypt-common.c' 'yescrypt-common.obj' | Out-Null
132+
Compile-One 'sha256.c' 'sha256.obj' | Out-Null
133+
Compile-One 'insecure_memzero.c' 'insecure_memzero.obj' | Out-Null
134+
}
135+
136+
function Build-Tests {
137+
param([string]$Core = $OBJS_CORE_OPT)
138+
Build-Common -Core $Core
139+
Compile-One 'tests.c' 'tests.obj' | Out-Null
140+
Link-Exe 'tests.exe' (@($Core) + $OBJS_COMMON + @('tests.obj'))
141+
Write-Host "Built tests.exe"
142+
}
143+
144+
function Build-PhcTest {
145+
param([string]$Core = $OBJS_CORE_OPT)
146+
Build-Common -Core $Core
147+
# phc-test is phc.c compiled with -DTEST (Makefile: phc-test.o).
148+
Compile-One 'phc.c' 'phc-test.obj' @('/DTEST') | Out-Null
149+
Link-Exe 'phc-test.exe' (@($Core) + $OBJS_COMMON + @('phc-test.obj'))
150+
Write-Host "Built phc-test.exe"
151+
}
152+
153+
# Read a file as text with CRLF normalized to LF (MSVC's text-mode output uses
154+
# CRLF; the known-good files use LF).
155+
function Get-NormalizedText {
156+
param([string]$Path)
157+
return (Get-Content -Raw -LiteralPath $Path) -replace "`r`n", "`n"
158+
}
159+
160+
function Invoke-Check {
161+
param([string]$Core = $OBJS_CORE_OPT)
162+
Build-Tests -Core $Core
163+
Build-PhcTest -Core $Core
164+
165+
Write-Host 'Running main tests'
166+
& .\tests.exe | Out-File -Encoding ascii -FilePath 'TESTS-OUT'
167+
if ($LASTEXITCODE -ne 0) { throw "tests.exe failed with exit code $LASTEXITCODE" }
168+
$expected = (Get-NormalizedText 'TESTS-OK').TrimEnd("`n")
169+
$actual = (Get-NormalizedText 'TESTS-OUT').TrimEnd("`n")
170+
if ($expected -eq $actual) {
171+
Write-Host 'PASSED' -ForegroundColor Green
172+
} else {
173+
Write-Host 'FAILED' -ForegroundColor Red
174+
Compare-Object ($expected -split "`n") ($actual -split "`n") |
175+
Format-Table -AutoSize | Out-String | Write-Host
176+
exit 1
177+
}
178+
179+
if (Test-Path -LiteralPath 'PHC-TEST-OK-SHA256') {
180+
Write-Host 'Running PHC tests'
181+
# Capture raw stdout (text mode -> CRLF), then verify the LF-normalized
182+
# SHA-256 against the known-good digest in PHC-TEST-OK-SHA256.
183+
& cmd /c ".\phc-test.exe > PHC-TEST-OUT 2>nul"
184+
if ($LASTEXITCODE -ne 0) { throw "phc-test.exe failed with exit code $LASTEXITCODE" }
185+
186+
$bytes = [System.Text.Encoding]::ASCII.GetBytes((Get-NormalizedText 'PHC-TEST-OUT'))
187+
$sha = [System.Security.Cryptography.SHA256]::Create()
188+
try {
189+
$got = -join ($sha.ComputeHash($bytes) | ForEach-Object { $_.ToString('x2') })
190+
} finally {
191+
$sha.Dispose()
192+
}
193+
$expectedHash = ((Get-Content -Raw 'PHC-TEST-OK-SHA256').Trim() -split '\s+')[0].ToLower()
194+
if ($got -eq $expectedHash) {
195+
Write-Host 'PHC PASSED' -ForegroundColor Green
196+
} else {
197+
Write-Host "PHC FAILED (got $got, expected $expectedHash)" -ForegroundColor Red
198+
exit 1
199+
}
200+
}
201+
}
202+
203+
function Invoke-Clean {
204+
$patterns = @('*.obj', 'tests.exe', 'phc-test.exe', 'TESTS-OUT', 'PHC-TEST-OUT',
205+
'*.ilk', '*.pdb')
206+
foreach ($p in $patterns) {
207+
Get-ChildItem -LiteralPath $PSScriptRoot -Filter $p -ErrorAction SilentlyContinue |
208+
Remove-Item -Force -ErrorAction SilentlyContinue
209+
}
210+
Write-Host 'Cleaned build artifacts.'
211+
}
212+
213+
# --- Dispatch ---------------------------------------------------------------
214+
215+
switch ($Target) {
216+
'clean' { Invoke-Clean; break }
217+
default {
218+
Import-VcEnvironment
219+
switch ($Target) {
220+
'all' { Build-Tests; Build-PhcTest }
221+
'tests' { Build-Tests }
222+
'phc-test' { Build-PhcTest }
223+
'check' { Invoke-Check }
224+
'ref' { Build-Tests -Core $OBJS_CORE_REF; Build-PhcTest -Core $OBJS_CORE_REF }
225+
'check-ref' { Invoke-Check -Core $OBJS_CORE_REF }
226+
}
227+
}
228+
}

phc.c

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,60 @@ int PHS(void *out, size_t outlen, const void *in, size_t inlen,
5252

5353
#ifdef TEST
5454
#include <stdio.h>
55+
#ifdef _MSC_VER
56+
/*
57+
* MSVC lacks the POSIX <unistd.h> and <sys/times.h> interfaces used for timing
58+
* below. Provide a minimal Win32-based shim; the timing it produces is only
59+
* printed to stderr, so it does not affect the hashes compared by the tests.
60+
*/
61+
#include <time.h>
62+
#include <windows.h>
63+
64+
struct tms {
65+
clock_t tms_utime;
66+
clock_t tms_stime;
67+
clock_t tms_cutime;
68+
clock_t tms_cstime;
69+
};
70+
71+
#define _SC_CLK_TCK 1
72+
73+
/* Report a 1 ms tick so wall- and CPU-times share the same units. */
74+
static long sysconf(int name)
75+
{
76+
(void)name;
77+
return 1000;
78+
}
79+
80+
/* 100 ns FILETIME units -> 1 ms ticks. */
81+
static clock_t filetime_to_ticks(const FILETIME *ft)
82+
{
83+
ULARGE_INTEGER t;
84+
t.LowPart = ft->dwLowDateTime;
85+
t.HighPart = ft->dwHighDateTime;
86+
return (clock_t)(t.QuadPart / 10000);
87+
}
88+
89+
/* Wall-clock as return value (1 ms ticks), process CPU times in *buf. */
90+
static clock_t times(struct tms *buf)
91+
{
92+
FILETIME creation, exit, kernel, user;
93+
LARGE_INTEGER freq, now;
94+
95+
GetProcessTimes(GetCurrentProcess(), &creation, &exit, &kernel, &user);
96+
buf->tms_utime = filetime_to_ticks(&user);
97+
buf->tms_stime = filetime_to_ticks(&kernel);
98+
buf->tms_cutime = 0;
99+
buf->tms_cstime = 0;
100+
101+
QueryPerformanceFrequency(&freq);
102+
QueryPerformanceCounter(&now);
103+
return (clock_t)(now.QuadPart * 1000 / freq.QuadPart);
104+
}
105+
#else
55106
#include <unistd.h> /* for sysconf() */
56107
#include <sys/times.h>
108+
#endif
57109

58110
static void print_hex(const uint8_t *buf, size_t buflen, const char *sep)
59111
{

0 commit comments

Comments
 (0)