Skip to content

Commit

Permalink
Add support for NVIDEA's multicore/GPU friendly PGCC compiler.
Browse files Browse the repository at this point in the history
  • Loading branch information
bwoodsend committed Nov 6, 2021
1 parent 683edc1 commit b0b4307
Show file tree
Hide file tree
Showing 10 changed files with 110 additions and 26 deletions.
25 changes: 25 additions & 0 deletions .github/workflows/test-pgcc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Install and test nvidia's PGCC with clug. Currently this compiler only
# supports Linux.
---
name: Test PGCC

on:
workflow_dispatch:

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

# The recommended way to install PGCC is just raw wget/paste - no package
# managers.
- run: |
wget -nv https://developer.download.nvidia.com/hpc-sdk/21.9/nvhpc_2021_219_Linux_x86_64_cuda_11.4.tar.gz
tar xpzf nvhpc_2021_219_Linux_x86_64_cuda_11.4.tar.gz
sudo nvhpc_2021_219_Linux_x86_64_cuda_11.4/install
echo PATH=$PATH:/opt/nvidia/hpc_sdk/Linux_x86_64/21.9/compilers/bin/ >> $GITHUB_ENV
working-directory: /opt/
- run: pip install -e .[test]
- run: CC=pgcc pytest
24 changes: 14 additions & 10 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -101,18 +101,20 @@ Before you commit yourself to any non Pure-Python you should bear in mind that:
Supported Compilers
-------------------

The following OS/compiler combinations are fully supported and tested regularly.

======== ===== ======= ===== ======= ============ ========
Compiler Linux Windows macOS FreeBSD Cygwin/msys2 Android*
======== ===== ======= ===== ======= ============ ========
gcc_ ✓ ✓ ✓ ✓ ✓ ✗
clang_ ✓ ✓ ✓ ✓ ✗ ✓
MSVC ✗ ✗ ✗ ✗ ✗ ✗
TinyCC_ ✓ ✓ ✗ ✗ ✗ ✗
======== ===== ======= ===== ======= ============ ========
The following OS/compiler combinations are fully supported and tested routinely.

========== ===== ======= ===== ======= ============ ========
Compiler Linux Windows macOS FreeBSD Cygwin/msys2 Android*
========== ===== ======= ===== ======= ============ ========
gcc_ ✓ ✓ ✓ ✓ ✓ ✗
clang_ ✓ ✓ ✓ ✓ ✗ ✓
MSVC ✗ ✗ ✗ ✗ ✗ ✗
TinyCC_ ✓ ✓ ✗ ✗ ✗ ✗
PGCC_ \*\* ✓ ✗ ✗ ✗ ✗ ✗
========== ===== ======= ===== ======= ============ ========

\* Using Termux_.
\*\* Installable as part of the `NVIDIA HPC SDK`_.

Installation
------------
Expand Down Expand Up @@ -203,3 +205,5 @@ Credits
.. _manylinux: https://github.com/pypa/manylinux/tree/manylinux1
.. _Termux: https://termux.com/
.. _WinLibs: https://www.winlibs.com/
.. _PGCC: https://docs.nvidia.com/hpc-sdk/pgi-compilers/20.4/x86/pgi-ref-guide/index.htm
.. _`NVIDIA HPC SDK`: https://developer.nvidia.com/hpc-sdk
26 changes: 21 additions & 5 deletions cslug/_cc.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,17 +96,31 @@ def cc_version(CC=None):
- ``'tcc'`` for `TinyCC`_ including the 32-bit ``i386-win32-tcc`` version.
- ``'clang'`` for clang_.
- ``'pcc'`` for `Portable C Compiler`_.
- ``'PGCC'`` for pgcc_.
The `version_info` is in the standard ``(major, minor, micro)`` version
format.
"""
cmd = [cc(CC), "-v"]
p = run(cmd, stdout=PIPE, stderr=PIPE)
CC = cc(CC)
# This function is split into two so that both ``$CC -v`` and
# ``$CC --version`` can be tried.
try:
return _cc_version(CC, "-v")
except RuntimeError: # pragma: no cover
# Currently, the only compiler to need this variant is pgcc.
return _cc_version(CC, "--version")


def _cc_version(*command):
"""Execute some form of ``$CC --what-are-you`` command and attempt to parse
the output."""
p = run(command, stdout=PIPE, stderr=PIPE)

# Some compilers use stdout and others use stderr - combine them.
stdout = p.stdout + p.stderr
return _parse_cc_version(stdout, cmd)

return _parse_cc_version(stdout, p.args)


def _parse_cc_version(stdout: bytes, cmd: list):
Expand All @@ -122,9 +136,11 @@ def _parse_cc_version(stdout: bytes, cmd: list):
(str, tuple[int]): A ``(name, version_info)`` pair.
"""
# All compilers except pcc use the format "[name] version [version]".
# Most compilers (namely gcc, clang and tcc) use the format
# "[name] version [version]" but pcc and pgcc have their own..
m = re.search(rb"(\S+) version ([\d.]+)", stdout) \
or re.search(rb"(pcc) ([\d.]+) for \S+", stdout)
or re.search(rb"(pcc) ([\d.]+) for \S+", stdout) \
or re.search(rb"(pgcc) \D* ([\d.]+)", stdout)

try:
name, version = m.groups()
Expand Down
14 changes: 12 additions & 2 deletions cslug/_cslug.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"pcc": "-rdynamic",
"gcc": "-fPIC",
"clang": "" if OS == "Windows" else "-fPIC",
"pgcc": "",
}


Expand Down Expand Up @@ -130,13 +131,14 @@ def compile(self):
# Close everything, wait for completion then check for error messages.
p.stdin.close()
p.wait()
msg = p.stderr.read()
errors = p.stderr.read()
p.stderr.close(), p.stdout.close()

# If we had to resort to using temporary files then clear them up.
for file in temporary_files: # pragma: no cover
os.remove(file.name)

msg = strip_useless_warnings(errors)
# If error message is just whitespace:
if not re.search(r"\S", msg):
# Make it empty.
Expand Down Expand Up @@ -336,7 +338,7 @@ def compile_command(self, _cc=None, _cc_version=None):
# For the compilers that do not support piped source code, convert all
# pseudo files to temporary files.
temporary_files = []
if cc_name in ("pcc",):
if cc_name in ("pcc", "pgcc"):
for buffer in buffers:
file = tempfile.NamedTemporaryFile("w", encoding="utf-8",
delete=False, suffix=".c")
Expand Down Expand Up @@ -395,3 +397,11 @@ def check_printfs(text, name=None):
out = True
i += 1
return out


def strip_useless_warnings(message):
"""Remove some known harmless warnings from an error/warning message."""
# These are both only issued by pgcc.
return re.sub(
r".*\.ld contains output sections; did you forget -T\?"
r"|.* last line of file ends without a newline.*\s*\^", "", message)
2 changes: 1 addition & 1 deletion docs/demos/globals/globals.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
assert a_bytes_array.value == b"Hello, my name is Ned."
if a_string.value != "Сәлам. Минем исемем Нед.":
cc_name, cc_version = _cc.cc_version()
if cc_name in ("tcc", "pcc"):
if cc_name in ("tcc", "pcc", "pgcc"):
pass
elif cc_name == "clang" and cc_version < (3, 3):
pass
Expand Down
2 changes: 2 additions & 0 deletions docs/source/rst_epilog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
.. _wheel: https://wheel.readthedocs.io/en/stable/
.. _`raise an issue on Github`: https://github.com/bwoodsend/cslug/issues/new
.. _WinLibs: https://www.winlibs.com/
.. _PGCC: https://docs.nvidia.com/hpc-sdk/pgi-compilers/20.4/x86/pgi-ref-guide/index.htm
.. _`NVIDIA HPC SDK`: https://developer.nvidia.com/hpc-sdk

.. role:: tooltip(raw)
:format: html
Expand Down
3 changes: 3 additions & 0 deletions tests/resources/cc_versions/pgcc-21.9-x86_64
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pgcc (aka nvc) 21.9-0 64-bit target on x86-64 Linux -tp skylake
PGI Compilers and Tools
Copyright (c) 2021, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
23 changes: 18 additions & 5 deletions tests/test_cc.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@

import pytest

from cslug._cc import cc, cc_version, which, _parse_cc_version, \
_macos_architecture
from cslug._cc import cc, which, _parse_cc_version, _macos_architecture
from cslug import exceptions, misc
from cslug._cslug import strip_useless_warnings

from tests import DUMP, uuid, RESOURCES

Expand Down Expand Up @@ -123,9 +123,12 @@ def test_no_cc_or_blocked_error():
@pytest.mark.parametrize("path", cc_version_files,
ids=[i.name for i in cc_version_files])
def test_cc_version_parse(path):
"""Test the parsing of ``$CC -v`` output. So that these can be easily tested
on all platforms, sample outputs of ``$CC -v`` are dumped in files located
at tests/resources/cc_versions/.
"""Test the parsing of ``$CC -v`` or ``$CC --version`` outputs.
So that these can be easily tested on all platforms, sample outputs of
``$CC -v`` or ``$CC --version`` are dumped in files located at
tests/resources/cc_versions.
"""
# The intended parse output is the filename.
name, version, _ = path.name.split("-")
Expand Down Expand Up @@ -162,3 +165,13 @@ def test_macos_architecture():
assert _macos_architecture("x86_64,arm64") == "universal2"
with pytest.raises(EnvironmentError, match="The MACOS_ARCHITECTURE .*"):
_macos_architecture("spaghetti")


def test_strip_useless_warnings():
assert strip_useless_warnings(
'"/tmp/05fa2e86-3c3a-11ec-a499-586c251818cf.c", line 6: '
'warning: last line of file ends without a newline\n ^') == ""

assert strip_useless_warnings(
'/opt/nvidia/hpc_sdk/Linux_x86_64/21.9/compilers/lib/nvhpc.ld '
'contains output sections; did you forget -T?') == ''
14 changes: 12 additions & 2 deletions tests/test_slugs.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

import pytest

from cslug import exceptions, anchor, CSlug, misc, Header
from cslug import exceptions, anchor, CSlug, misc, Header, cc_version

from tests import DUMP, name, DEMOS, RESOURCES, warnings_are_evil
from tests.test_pointers import leaks
Expand Down Expand Up @@ -527,10 +527,20 @@ def test_custom_include():
assert self.dll.foo() == 13

# Test without the custom include path. It should lead to a failed build.
# This awkward construct asserts that an error is raised however pgcc
# indeterminantly ignores such errors and produces non functional
# exectaubles. In this case we should xfail().
self.flags.clear()
with pytest.raises(exceptions.BuildError):
try:
self.make()

if cc_version()[0] == "pgcc":
pytest.xfail("pgcc silently created an invalid executable.")
else:
assert 0, "No build error was raised"
except exceptions.BuildError:
return


@warnings_are_evil
def test_macos_arches(monkeypatch):
Expand Down
3 changes: 2 additions & 1 deletion tests/test_unicode.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def test_escaped_unicode_literal(source):
def test_non_escaped_unicode_literal(source):
"""Test a C source file may contain unicode characters in string literals.
"""
delayed_skip_if_unsupported("Unicode literals", pgcc=False)
self = CSlug(anchor(name()), source)
self.make()
assert self.dll.a() == "㟐"
Expand All @@ -90,7 +91,7 @@ def test_non_escaped_unicode_literal(source):
def test_unicode_identifiers(source):
"""Test unicode function/variable names."""
delayed_skip_if_unsupported("Unicode identifiers", gcc=(10,), tcc=False,
pcc=False, clang=(3, 3))
pcc=False, clang=(3, 3), pgcc=False)

slug = CSlug(anchor(name()), source)
slug.make()
Expand Down

0 comments on commit b0b4307

Please sign in to comment.