Skip to content

Commit

Permalink
[init] Adding CUDA language/compiler and CodePrinter (#32)
Browse files Browse the repository at this point in the history
This PR aims to make the C code compilable using nvcc. The cuda language was added as well as a CudaCodePrinter.

Changes to stdlib:

Wrapped expressions using complex types in an `ifndef __NVCC__` to avoid processing them with the nvcc compiler

---------

Co-authored-by: Mouad Elalj, EmilyBourne
  • Loading branch information
bauom authored and EmilyBourne committed Mar 11, 2024
1 parent 4f08263 commit d9a1fb5
Show file tree
Hide file tree
Showing 19 changed files with 324 additions and 94 deletions.
1 change: 1 addition & 0 deletions .dict_custom.txt
Original file line number Diff line number Diff line change
Expand Up @@ -106,5 +106,6 @@ Valgrind
variadic
subclasses
oneAPI
Cuda
getter
setter
4 changes: 2 additions & 2 deletions .github/actions/pytest_parallel/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ runs:
steps:
- name: Test with pytest
run: |
mpiexec -n 4 ${MPI_OPTS} python -m pytest epyccel/test_parallel_epyccel.py -v -m parallel -rXx
#mpiexec -n 4 ${MPI_OPTS} python -m pytest epyccel -v -m parallel -rXx
mpiexec -n 4 ${MPI_OPTS} python -m pytest epyccel/test_parallel_epyccel.py -v -m "parallel and not cuda" -rXx
#mpiexec -n 4 ${MPI_OPTS} python -m pytest epyccel -v -m "parallel and not cuda" -rXx
shell: ${{ inputs.shell_cmd }}
working-directory: ./tests

4 changes: 2 additions & 2 deletions .github/actions/pytest_run/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,13 @@ runs:
working-directory: ./tests
id: pytest_3
- name: Test Fortran translations
run: python -m pytest -n auto -rX ${FLAGS} -m "not (parallel or xdist_incompatible) and not (c or python or ccuda) ${{ inputs.pytest_mark }}" --ignore=symbolic --ignore=ndarrays 2>&1 | tee s4_outfile.out
run: python -m pytest -n auto -rX ${FLAGS} -m "not (parallel or xdist_incompatible) and not (c or python or cuda) ${{ inputs.pytest_mark }}" --ignore=symbolic --ignore=ndarrays 2>&1 | tee s4_outfile.out
shell: ${{ inputs.shell_cmd }}
working-directory: ./tests
id: pytest_4
- name: Test multi-file Fortran translations
run: |
python -m pytest -rX ${FLAGS} -m "xdist_incompatible and not parallel and not (c or python or ccuda) ${{ inputs.pytest_mark }}" --ignore=symbolic --ignore=ndarrays | tee s5_outfile.out
python -m pytest -rX ${FLAGS} -m "xdist_incompatible and not parallel and not (c or python or cuda) ${{ inputs.pytest_mark }}" --ignore=symbolic --ignore=ndarrays | tee s5_outfile.out
pyccel-clean
shell: ${{ inputs.shell_cmd }}
working-directory: ./tests
Expand Down
11 changes: 9 additions & 2 deletions .github/actions/pytest_run_cuda/action.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: 'Pyccel pytest commands generating Ccuda'
name: 'Pyccel pytest commands generating Cuda'
inputs:
shell_cmd:
description: 'Specifies the shell command (different for anaconda)'
Expand All @@ -11,7 +11,14 @@ runs:
- name: Ccuda tests with pytest
run: |
# Catch exit 5 (no tests found)
sh -c 'python -m pytest -n auto -rx -m "not (parallel or xdist_incompatible) and ccuda" --ignore=symbolic --ignore=ndarrays; ret=$?; [ $ret = 5 ] && exit 0 || exit $ret'
python -m pytest -rX ${FLAGS} -m "not (xdist_incompatible or parallel) and cuda ${{ inputs.pytest_mark }}" --ignore=symbolic --ignore=ndarrays 2>&1 | tee s1_outfile.out
pyccel-clean
shell: ${{ inputs.shell_cmd }}
working-directory: ./tests
- name: Final step
if: always()
id: status
run:
python ci_tools/json_pytest_output.py -t "Cuda Test Summary" --tests "Cuda tests:${{ steps.pytest_1.outcome }}:tests/s1_outfile.out"

shell: ${{ inputs.shell_cmd }}
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# Change Log
All notable changes to this project will be documented in this file.

## \[Cuda - UNRELEASED\]

### Added

- #32 : add support for `nvcc` Compiler and `cuda` language as a possible option.

## \[UNRELEASED\]

### Added
Expand Down
2 changes: 1 addition & 1 deletion ci_tools/json_pytest_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def mini_md_summary(title, outcome, failed_tests):
summary = ""

failed_pattern = re.compile(r".*FAILED.*")
languages = ('c', 'fortran', 'python')
languages = ('c', 'fortran', 'python', 'cuda')
pattern = {lang: re.compile(r".*\["+lang+r"\]\ \_.*") for lang in languages}

for i in p_args.tests:
Expand Down
43 changes: 30 additions & 13 deletions pyccel/codegen/codegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,41 @@
from pyccel.codegen.printing.fcode import FCodePrinter
from pyccel.codegen.printing.ccode import CCodePrinter
from pyccel.codegen.printing.pycode import PythonCodePrinter
from pyccel.codegen.printing.cucode import CudaCodePrinter

from pyccel.ast.core import FunctionDef, Interface, ModuleHeader
from pyccel.errors.errors import Errors
from pyccel.utilities.stage import PyccelStage

_extension_registry = {'fortran': 'f90', 'c':'c', 'python':'py'}
_header_extension_registry = {'fortran': None, 'c':'h', 'python':None}
_extension_registry = {'fortran': 'f90', 'c':'c', 'python':'py', 'cuda':'cu'}
_header_extension_registry = {'fortran': None, 'c':'h', 'python':None, 'cuda':'h'}
printer_registry = {
'fortran':FCodePrinter,
'c':CCodePrinter,
'python':PythonCodePrinter
'python':PythonCodePrinter,
'cuda':CudaCodePrinter
}

pyccel_stage = PyccelStage()

class Codegen(object):

"""Abstract class for code generator."""
"""
Class which handles the generation of code.
def __init__(self, parser, name):
"""Constructor for Codegen.
parser: pyccel parser
The class which handles the generation of code. This is done by creating an appropriate class
inheriting from `CodePrinter` and using it to create strings describing the code that should
be printed. This class then takes care of creating the necessary files.
Parameters
----------
parser : SemanticParser
The Pyccel Semantic parser node.
name : str
Name of the generated module or program.
"""

name: str
name of the generated module or program.
"""
def __init__(self, parser, name):
pyccel_stage.set_stage('codegen')
self._parser = parser
self._ast = parser.ast
Expand Down Expand Up @@ -135,12 +142,22 @@ def language(self):
return self._language

def set_printer(self, **settings):
""" Set the current codeprinter instance"""
"""
Set the current codeprinter instance.
Getting the language that will be used (default language used is fortran),
Then instantiating the codePrinter with the corresponding language.
Parameters
----------
**settings : dict
Any additional arguments which are necessary for CCodePrinter.
"""
# Get language used (default language used is fortran)
language = settings.pop('language', 'fortran')

# Set language
if not language in ['fortran', 'c', 'python']:
if not language in ['fortran', 'c', 'python', 'cuda']:
raise ValueError('{} language is not available'.format(language))
self._language = language

Expand Down
5 changes: 4 additions & 1 deletion pyccel/codegen/compiling/compilers.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,10 @@ def compile_shared_library(self, compile_obj, output_folder, verbose = False, sh
# Collect compile information
exec_cmd, includes, libs_flags, libdirs_flags, m_code = \
self._get_compile_components(compile_obj, accelerators)
linker_libdirs_flags = ['-Wl,-rpath' if l == '-L' else l for l in libdirs_flags]
if self._info['exec'] == 'nvcc':
linker_libdirs_flags = ['-Xcompiler' if l == '-L' else f'"-Wl,-rpath,{l}"' for l in libdirs_flags]
else:
linker_libdirs_flags = ['-Wl,-rpath' if l == '-L' else l for l in libdirs_flags]

flags.insert(0,"-shared")

Expand Down
5 changes: 3 additions & 2 deletions pyccel/codegen/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,10 @@ def handle_error(stage):
if language is None:
language = 'fortran'

# Choose Fortran compiler
# Choose Default compiler
if compiler is None:
compiler = os.environ.get('PYCCEL_DEFAULT_COMPILER', 'GNU')
default_compiler_family = 'nvidia' if language == 'cuda' else 'GNU'
compiler = os.environ.get('PYCCEL_DEFAULT_COMPILER', default_compiler_family)

fflags = [] if fflags is None else fflags.split()
wrapper_flags = [] if wrapper_flags is None else wrapper_flags.split()
Expand Down
74 changes: 74 additions & 0 deletions pyccel/codegen/printing/cucode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# coding: utf-8
#------------------------------------------------------------------------------------------#
# This file is part of Pyccel which is released under MIT License. See the LICENSE file or #
# go to https://github.com/pyccel/pyccel/blob/master/LICENSE for full license details. #
#------------------------------------------------------------------------------------------#
"""
Provide tools for generating and handling CUDA code.
This module is designed to interface Pyccel's Abstract Syntax Tree (AST) with CUDA,
enabling the direct translation of high-level Pyccel expressions into CUDA code.
"""

from pyccel.codegen.printing.ccode import CCodePrinter, c_library_headers

from pyccel.ast.core import Import, Module

from pyccel.errors.errors import Errors


errors = Errors()

__all__ = ["CudaCodePrinter"]

class CudaCodePrinter(CCodePrinter):
"""
Print code in CUDA format.
This printer converts Pyccel's Abstract Syntax Tree (AST) into strings of CUDA code.
Navigation through this file utilizes _print_X functions,
as is common with all printers.
Parameters
----------
filename : str
The name of the file being pyccelised.
prefix_module : str
A prefix to be added to the name of the module.
"""
language = "cuda"

def __init__(self, filename, prefix_module = None):

errors.set_target(filename, 'file')

super().__init__(filename)

def _print_Module(self, expr):
self.set_scope(expr.scope)
self._current_module = expr.name
body = ''.join(self._print(i) for i in expr.body)

global_variables = ''.join(self._print(d) for d in expr.declarations)

# Print imports last to be sure that all additional_imports have been collected
imports = [Import(expr.name, Module(expr.name,(),())), *self._additional_imports.values()]
c_headers_imports = ''
local_imports = ''

for imp in imports:
if imp.source in c_library_headers:
c_headers_imports += self._print(imp)
else:
local_imports += self._print(imp)

imports = f'{c_headers_imports}\
extern "C"{{\n\
{local_imports}\
}}'

code = f'{imports}\n\
{global_variables}\n\
{body}\n'

self.exit_scope()
return code
2 changes: 1 addition & 1 deletion pyccel/commands/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def pyccel(files=None, mpi=None, openmp=None, openacc=None, output_dir=None, com
# ... backend compiler options
group = parser.add_argument_group('Backend compiler options')

group.add_argument('--language', choices=('fortran', 'c', 'python'), help='Generated language')
group.add_argument('--language', choices=('fortran', 'c', 'python', 'cuda'), help='Generated language')

group.add_argument('--compiler', help='Compiler family or json file containing a compiler description {GNU,intel,PGI}')

Expand Down
13 changes: 12 additions & 1 deletion pyccel/compilers/default_compilers.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,15 @@
},
'family': 'nvidia',
}
#------------------------------------------------------------
nvcc_info = {'exec' : 'nvcc',
'language' : 'cuda',
'debug_flags' : ("-g",),
'release_flags': ("-O3",),
'general_flags': ('--compiler-options', '-fPIC',),
'family' : 'nvidia'
}


#------------------------------------------------------------
def change_to_lib_flag(lib):
Expand Down Expand Up @@ -288,6 +297,7 @@ def change_to_lib_flag(lib):
pgfortran_info.update(python_info)
nvc_info.update(python_info)
nvfort_info.update(python_info)
nvcc_info.update(python_info)

available_compilers = {('GNU', 'c') : gcc_info,
('GNU', 'fortran') : gfort_info,
Expand All @@ -296,6 +306,7 @@ def change_to_lib_flag(lib):
('PGI', 'c') : pgcc_info,
('PGI', 'fortran') : pgfortran_info,
('nvidia', 'c') : nvc_info,
('nvidia', 'fortran') : nvfort_info}
('nvidia', 'fortran') : nvfort_info,
('nvidia', 'cuda'): nvcc_info}

vendors = ('GNU','intel','PGI','nvidia')
4 changes: 3 additions & 1 deletion pyccel/naming/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
from .fortrannameclashchecker import FortranNameClashChecker
from .cnameclashchecker import CNameClashChecker
from .pythonnameclashchecker import PythonNameClashChecker
from .cudanameclashchecker import CudaNameClashChecker

name_clash_checkers = {'fortran':FortranNameClashChecker(),
'c':CNameClashChecker(),
'python':PythonNameClashChecker()}
'python':PythonNameClashChecker(),
'cuda':CudaNameClashChecker()}
Loading

0 comments on commit d9a1fb5

Please sign in to comment.