Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #580 by using a fixed-point implementation for unit conversions using integer representations #615

Draft
wants to merge 39 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
cf9c05f
wip
burnpanck Sep 15, 2024
3635260
disabled two tests which now trigger #614
burnpanck Sep 15, 2024
74f30da
again; fix C++20 compatibility
burnpanck Sep 15, 2024
2f54c76
addessed most review concerns, fixed CI failure
burnpanck Sep 16, 2024
e003a58
fixed and expanded double_width_int implemenation, tried to fix a bug…
burnpanck Sep 16, 2024
60c94da
one more try
burnpanck Sep 16, 2024
d00b330
fixed pedantic error
burnpanck Sep 16, 2024
99d4315
Merge remote-tracking branch 'upstream/master' into feature/fixed-poi…
burnpanck Nov 5, 2024
653d3d2
fix formatting issues
burnpanck Nov 5, 2024
ed2574f
allow use of __(u)int128, and always use std::bit_width and friends
burnpanck Nov 5, 2024
e688ffc
silence pedantic warning about __int128
burnpanck Nov 5, 2024
55d8fd6
cross-platform silencing of pedantic warning
burnpanck Nov 5, 2024
38dcf64
Merge remote-tracking branch 'upstream/master' into feature/fixed-poi…
burnpanck Nov 6, 2024
ad76149
Apply suggestions from code review
burnpanck Nov 6, 2024
95cc9f3
more review-requested changes, good test-coverage of double_width_int…
burnpanck Nov 6, 2024
5f8eb5c
made hi_ and lo_ private members of double_width_int
burnpanck Nov 6, 2024
1b57404
attempt to fix tests on apple clang
burnpanck Nov 6, 2024
f673619
try to work around issues around friend instantiations of double_widt…
burnpanck Nov 6, 2024
f642d37
fix: gcc-12 friend compilation issue workaround
mpusz Nov 9, 2024
b6a6752
implement dedicated facilities to customise scaling of numbers with m…
burnpanck Nov 10, 2024
647ce6b
fixed a few more details
burnpanck Nov 10, 2024
464ecd4
Merge remote-tracking branch 'upstream/master' into feature/fixed-poi…
burnpanck Nov 10, 2024
e933be7
fix a few issues uncovered in CI
burnpanck Nov 11, 2024
6873c8b
fix formatting
burnpanck Nov 11, 2024
65a0ee4
fix module exports - does not yet inlude other review input
burnpanck Nov 11, 2024
0c1971e
addressed most review input
burnpanck Nov 11, 2024
4ef0210
fix includes (and use curly braces for constructor calls in measurmen…
burnpanck Nov 12, 2024
35ed472
first attempt at generating sparse CI run matrix in python; also, can…
burnpanck Nov 12, 2024
329b9f5
Merge branch 'master' into feature/faster-CI
burnpanck Nov 12, 2024
7fa15d2
fix formatting
burnpanck Nov 12, 2024
e464677
don't test Clang 19 just yet; fix cancel-in-progres
burnpanck Nov 12, 2024
cc9ea9d
add cancel-in-progress to all workflows
burnpanck Nov 12, 2024
a51462c
missing checkout in generate-matrix step
burnpanck Nov 12, 2024
f4c8e90
fix boolean conan options in dynamic CI matrix
burnpanck Nov 12, 2024
01f44c6
heed github warning, and use output file instead of set-output comman…
burnpanck Nov 12, 2024
5713243
fix clang 16
burnpanck Nov 12, 2024
ff11878
exclude clang18+debug from freestanding again
burnpanck Nov 12, 2024
b35e241
fix clang on macos-14 (arm64)
burnpanck Nov 12, 2024
ef0e7b3
Merge branch 'feature/faster-CI' into feature/fixed-point-multiplicat…
burnpanck Nov 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ ignore =
E712,
# line break before binary operator
W503
per-file-ignores =
# flake8 is just plain wrong here, contradicting black
.github/generate-job-matrix.py:E225,E231
206 changes: 206 additions & 0 deletions .github/generate-job-matrix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import argparse
import json
import os
import random
import typing
from types import SimpleNamespace

from job_matrix import CombinationCollector, Compiler, Configuration


def make_gcc_config(version: int) -> Configuration:
return Configuration(
name=f"GCC-{version}",
os="ubuntu-24.04",
compiler=Compiler(
type="GCC",
version=version,
cc=f"gcc-{version}",
cxx=f"g++-{version}",
),
cxx_modules=False,
std_format_support=version >= 13,
)


def make_clang_config(
version: int, platform: typing.Literal["x86-64", "arm64"] = "x86-64"
) -> Configuration:
cfg = SimpleNamespace(
name=f"Clang-{version} ({platform})",
compiler=SimpleNamespace(
type="CLANG",
version=version,
),
lib="libc++",
cxx_modules=version >= 17,
std_format_support=version >= 17,
)
match platform:
case "x86-64":
cfg.os = "ubuntu-22.04" if version < 17 else "ubuntu-24.04"
cfg.compiler.cc = f"clang-{version}"
cfg.compiler.cxx = f"clang++-{version}"
case "arm64":
cfg.os = "macos-14"
pfx = f"/opt/homebrew/opt/llvm@{version}/bin"
cfg.compiler.cc = f"{pfx}/clang"
cfg.compiler.cxx = f"{pfx}/clang++"
case _:
raise KeyError(f"Unsupported platform {platform!r} for Clang")
ret = cfg
ret.compiler = Compiler(**vars(cfg.compiler))
return Configuration(**vars(ret))


def make_apple_clang_config(version: int) -> Configuration:
ret = Configuration(
name=f"Apple Clang {version}",
os="macos-13",
compiler=Compiler(
type="APPLE_CLANG",
version=f"{version}.0",
cc="clang",
cxx="clang++",
),
cxx_modules=False,
std_format_support=False,
)
return ret


def make_msvc_config(release: str, version: int) -> Configuration:
ret = Configuration(
name=f"MSVC {release}",
os="windows-2022",
compiler=Compiler(
type="MSVC",
version=version,
cc="",
cxx="",
),
cxx_modules=False,
std_format_support=True,
)
return ret


configs = {
c.name: c
for c in [make_gcc_config(ver) for ver in [12, 13, 14]]
+ [
make_clang_config(ver, platform)
for ver in [16, 17, 18]
for platform in ["x86-64", "arm64"]
# arm64 runners are expensive; only consider one version
if ver == 18 or platform != "arm64"
]
+ [make_apple_clang_config(ver) for ver in [15]]
+ [make_msvc_config(release="14.4", version=194)]
}

full_matrix = dict(
config=list(configs.values()),
std=[20, 23],
formatting=["std::format", "fmtlib"],
contracts=["none", "gsl-lite", "ms-gsl"],
build_type=["Release", "Debug"],
)


def main():
parser = argparse.ArgumentParser()
# parser.add_argument("-I","--include",nargs="+",action="append")
# parser.add_argument("-X","--exclude",nargs="+",action="append")
parser.add_argument("--seed", type=int, default=42)
parser.add_argument("--preset", default=None)
parser.add_argument("--debug", nargs="+", default=["combinations"])
parser.add_argument("--suppress-output", default=False, action="store_true")

args = parser.parse_args()

rgen = random.Random(args.seed)

collector = CombinationCollector(
full_matrix,
hard_excludes=lambda e: (
e.formatting == "std::format" and not e.config.std_format_support
),
)
match args.preset:
case None:
pass
case "all":
collector.all_combinations()
case "conan" | "cmake":
collector.all_combinations(
formatting="std::format",
contracts="gsl-lite",
build_type="Debug",
std=20,
)
collector.all_combinations(
filter=lambda me: not me.config.std_format_support,
formatting="fmtlib",
contracts="gsl-lite",
build_type="Debug",
std=20,
)
collector.sample_combinations(rgen=rgen, min_samples_per_value=2)
case "clang-tidy":
collector.all_combinations(config=configs["Clang-18 (x86-64)"])
case "freestanding":
# TODO For some reason Clang-18 Debug with -ffreestanding does not pass CMakeTestCXXCompiler
collector.all_combinations(
filter=lambda e: not (
e.config.name.startswith("Clang-18") and e.build_type == "Debug"
),
config=[configs[c] for c in ["GCC-14", "Clang-18 (x86-64)"]],
contracts="none",
std=23,
)
case _:
raise KeyError(f"Unsupported preset {args.preset!r}")

if not collector.combinations:
raise ValueError("No combination has been produced")

data = sorted(collector.combinations)

json_data = [e.as_json() for e in data]

output_file = os.environ.get("GITHUB_OUTPUT")
if not args.suppress_output:
if output_file:
print(f"Writing outputs to {output_file}")
with open(output_file, "wt") as fh:
fh.write(f"matrix={json.dumps(json_data)}")
else:
print("No output file received!")

for dbg in args.debug:
match dbg:
case "yaml":
import yaml

json_data = json.loads(json.dumps(json_data))
print(yaml.safe_dump(json_data))
case "json":
print(json.dumps(json_data, indent=4))
case "combinations":
for e in data:
print(
f"{e.config!s:17s} c++{e.std:2d} {e.formatting:11s} {e.contracts:8s} {e.build_type:8s}"
)
case "counts":
print(f"Total combinations {len(data)}")
for (k, v), n in sorted(collector.per_value_counts.items()):
print(f" {k}={v}: {n}")
case "none":
pass
case _:
raise KeyError(f"Unknown debug mode {dbg!r}")


if __name__ == "__main__":
main()
139 changes: 139 additions & 0 deletions .github/job_matrix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import dataclasses
import itertools
import random
import typing
from dataclasses import dataclass


@dataclass(frozen=True, order=True)
class Compiler:
type: typing.Literal["GCC", "CLANG", "APPLE_CLANG", "MSVC"]
version: str | int
cc: str
cxx: str


@dataclass(frozen=True, order=True)
class Configuration:
name: str
os: str
compiler: Compiler
cxx_modules: bool
std_format_support: bool
conan_config: str = ""
lib: typing.Literal["libc++", "libstdc++"] | None = None

def __str__(self):
return self.name


@dataclass(frozen=True, order=True)
class MatrixElement:
config: Configuration
std: typing.Literal[20, 23]
formatting: typing.Literal["std::format", "fmtlib"]
contracts: typing.Literal["none", "gsl-lite", "ms-gsl"]
build_type: typing.Literal["Release", "Debug"]

def as_json(self):
def dataclass_to_json(obj):
"""Convert dataclasses to something json-serialisable"""
if dataclasses.is_dataclass(obj):
return {
k: dataclass_to_json(v) for k, v in dataclasses.asdict(obj).items()
}
return obj

ret = dataclass_to_json(self)
# patch boolean conan configuration options
config = ret["config"]
for k in ["cxx_modules"]:
config[k] = "True" if config[k] else "False"
return ret


class CombinationCollector:
"""Incremental builder of MatrixElements, allowing successive selection of entries."""

def __init__(
self,
full_matrix: dict[str, list[typing.Any]],
*,
hard_excludes: typing.Callable[[MatrixElement], bool] | None = None,
):
self.full_matrix = full_matrix
self.hard_excludes = hard_excludes
self.combinations: set[MatrixElement] = set()
self.per_value_counts: dict[tuple[str, typing.Any], int] = {
(k, v): 0 for k, options in full_matrix.items() for v in options
}

def _make_submatrix(self, **overrides):
new_matrix = dict(self.full_matrix)
for k, v in overrides.items():
if not isinstance(v, list):
v = [v]
new_matrix[k] = v
return new_matrix

def _add_combination(self, e: MatrixElement):
if e in self.combinations or (
self.hard_excludes is not None and self.hard_excludes(e)
):
return
self.combinations.add(e)
# update per_value_counts
for k, v in vars(e).items():
idx = (k, v)
self.per_value_counts[idx] = self.per_value_counts.get(idx, 0) + 1

def all_combinations(
self,
*,
filter: typing.Callable[[MatrixElement], bool] | None = None,
**overrides,
):
"""Adds all combinations in the submatrix defined by `overrides`."""
matrix = self._make_submatrix(**overrides)
keys = tuple(matrix.keys())
for combination in itertools.product(*matrix.values()):
cand = MatrixElement(**dict(zip(keys, combination)))
if filter and not filter(cand):
continue
self._add_combination(cand)

def sample_combinations(
self,
*,
rgen: random.Random,
min_samples_per_value: int = 1,
filter: typing.Callable[[MatrixElement], bool] | None = None,
**overrides,
):
"""Adds samples from the submatrix defined by `overrides`,
ensuring each individual value appears at least n times.
"""
matrix = self._make_submatrix(**overrides)
missing: dict[tuple[str, typing.Any], int] = {}
for key, options in matrix.items():
for value in options:
idx = (key, value)
missing[idx] = min_samples_per_value - self.per_value_counts.get(idx, 0)
while missing:
(force_key, force_option), remaining = next(iter(missing.items()))
if remaining <= 0:
missing.pop((force_key, force_option))
continue
choice = {}
for key, options in matrix.items():
choice[key] = force_option if key == force_key else rgen.choice(options)
cand = MatrixElement(**choice)
if filter and not filter(cand):
continue
self._add_combination(cand)
for idx in choice.items():
if missing.pop(idx, 0) <= 0:
continue
remaining = min_samples_per_value - self.per_value_counts.get(idx, 0)
if remaining > 0:
missing[idx] = remaining
Loading
Loading