Source code for ffc.codegeneration

# -*- coding: utf-8 -*-
"""
Compiler stage 4: Code generation
---------------------------------

This module implements the generation of C++ code for the body of each
UFC function from an (optimized) intermediate representation (OIR).
"""

# Copyright (C) 2009-2017 Anders Logg, Martin Sandve Aln├Žs, Marie E. Rognes,
# Kristian B. Oelgaard, and others
#
# This file is part of FFC.
#
# FFC is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# FFC is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with FFC. If not, see <http://www.gnu.org/licenses/>.

from itertools import chain
from ufl import product

# FFC modules
from ffc.log import info, begin, end, debug_code, dstr

# FFC code generation modules
from ffc.representation import pick_representation, ufc_integral_types

import ffc.uflacs.language.cnodes as L
from ffc.uflacs.language.format_lines import format_indented_lines
from ffc.uflacs.backends.ufc.utils import generate_error
from ffc.uflacs.backends.ufc.generators import ufc_integral, ufc_finite_element, ufc_dofmap, ufc_coordinate_mapping, ufc_form

[docs]def generate_code(ir, parameters): "Generate code from intermediate representation." begin("Compiler stage 4: Generating code") full_ir = ir # FIXME: This has global side effects # Set code generation parameters # set_float_formatting(parameters["precision"]) # set_exception_handling(parameters["convert_exceptions_to_warnings"]) # Extract representations ir_finite_elements, ir_dofmaps, ir_coordinate_mappings, ir_integrals, ir_forms = ir # Generate code for finite_elements info("Generating code for %d finite_element(s)" % len(ir_finite_elements)) code_finite_elements = [_generate_finite_element_code(ir, parameters) for ir in ir_finite_elements] # Generate code for dofmaps info("Generating code for %d dofmap(s)" % len(ir_dofmaps)) code_dofmaps = [_generate_dofmap_code(ir, parameters) for ir in ir_dofmaps] # Generate code for coordinate_mappings info("Generating code for %d coordinate_mapping(s)" % len(ir_coordinate_mappings)) code_coordinate_mappings = [_generate_coordinate_mapping_code(ir, parameters) for ir in ir_coordinate_mappings] # Generate code for integrals info("Generating code for integrals") code_integrals = [_generate_integral_code(ir, parameters) for ir in ir_integrals] # Generate code for forms info("Generating code for forms") code_forms = [_generate_form_code(ir, parameters) for ir in ir_forms] # Extract additional includes includes = _extract_includes(full_ir, code_integrals) end() return (code_finite_elements, code_dofmaps, code_coordinate_mappings,
code_integrals, code_forms, includes) def _extract_includes(full_ir, code_integrals): ir_finite_elements, ir_dofmaps, ir_coordinate_mappings, ir_integrals, ir_forms = full_ir # Includes added by representations includes = set() for code in code_integrals: includes.update(code["additional_includes_set"]) # Includes for dependencies in jit mode jit = any(full_ir[i][j]["jit"] for i in range(len(full_ir)) for j in range(len(full_ir[i]))) if jit: dep_includes = set() for ir in ir_finite_elements: dep_includes.update(_finite_element_jit_includes(ir)) for ir in ir_dofmaps: dep_includes.update(_dofmap_jit_includes(ir)) for ir in ir_coordinate_mappings: dep_includes.update(_coordinate_mapping_jit_includes(ir)) #for ir in ir_integrals: # dep_includes.update(_integral_jit_includes(ir)) for ir in ir_forms: dep_includes.update(_form_jit_includes(ir)) includes.update(['#include "%s"' % inc for inc in dep_includes]) return includes def _finite_element_jit_includes(ir): classnames = ir["create_sub_element"] postfix = "_finite_element" return [classname.rpartition(postfix)[0] + ".h" for classname in classnames] def _dofmap_jit_includes(ir): classnames = ir["create_sub_dofmap"] postfix = "_dofmap" return [classname.rpartition(postfix)[0] + ".h" for classname in classnames] def _coordinate_mapping_jit_includes(ir): classnames = [ ir["coordinate_finite_element_classname"], ir["scalar_coordinate_finite_element_classname"] ] postfix = "_finite_element" return [classname.rpartition(postfix)[0] + ".h" for classname in classnames] def _form_jit_includes(ir): # Gather all header names for classes that are separately compiled # For finite_element and dofmap the module and header name is the prefix, # extracted here with .split, and equal for both classes so we skip dofmap here: classnames = list(chain( ir["create_finite_element"], ir["create_coordinate_finite_element"] )) postfix = "_finite_element" includes = [classname.rpartition(postfix)[0] + ".h" for classname in classnames] classnames = ir["create_coordinate_mapping"] postfix = "_coordinate_mapping" includes += [classname.rpartition(postfix)[0] + ".h" for classname in classnames] return includes tt_timing_template = """ // Initialize timing variables static const std::size_t _tperiod = 10000; static std::size_t _tcount = 0; static auto _tsum = std::chrono::nanoseconds::zero(); static auto _tavg_best = std::chrono::nanoseconds::max(); static auto _tmin = std::chrono::nanoseconds::max(); static auto _tmax = std::chrono::nanoseconds::min(); // Measure single kernel time auto _before = std::chrono::high_resolution_clock::now(); { // Begin original kernel %s } // End original kernel // Measure single kernel time auto _after = std::chrono::high_resolution_clock::now(); // Update time stats const std::chrono::seconds _s(1); auto _tsingle = _after - _before; ++_tcount; _tsum += _tsingle; _tmin = std::min(_tmin, _tsingle); _tmax = std::max(_tmax, _tsingle); if (_tcount %% _tperiod == 0 || _tsum > _s) { // Record best average across batches std::chrono::nanoseconds _tavg = _tsum / _tcount; if (_tavg_best > _tavg) _tavg_best = _tavg; // Convert to ns auto _tot_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(_tsum).count(); auto _avg_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(_tavg).count(); auto _min_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(_tmin).count(); auto _max_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(_tmax).count(); auto _avg_best_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(_tavg_best).count(); // Print report std::cout << "FFC tt time:" << " avg_best = " << _avg_best_ns << " ns," << " avg = " << _avg_ns << " ns," << " min = " << _min_ns << " ns," << " max = " << _max_ns << " ns," << " tot = " << _tot_ns << " ns," << " n = " << _tcount << std::endl; // Reset statistics for next batch _tcount = 0; _tsum = std::chrono::nanoseconds(0); _tmin = std::chrono::nanoseconds::max(); _tmax = std::chrono::nanoseconds::min(); } """ def _generate_tabulate_tensor_comment(ir, parameters): "Generate comment for tabulate_tensor." r = ir["representation"] integrals_metadata = ir["integrals_metadata"] integral_metadata = ir["integral_metadata"] comment = [ L.Comment("This function was generated using '%s' representation" % r), L.Comment("with the following integrals metadata:"), L.Comment(""), L.Comment("\n".join(dstr(integrals_metadata).split("\n")[:-1])) ] for i, metadata in enumerate(integral_metadata): comment += [ L.Comment(""), L.Comment("and the following integral %d metadata:" % i), L.Comment(""), L.Comment("\n".join(dstr(metadata).split("\n")[:-1])) ] return format_indented_lines(L.StatementList(comment).cs_format(0), 1) def _generate_integral_code(ir, parameters): "Generate code for integrals from intermediate representation." # Select representation r = pick_representation(ir["representation"]) # Generate code # TODO: Drop prefix argument and get from ir: code = r.generate_integral_code(ir, ir["prefix"], parameters) # Hack for benchmarking overhead in assembler with empty # tabulate_tensor if parameters["generate_dummy_tabulate_tensor"]: code["tabulate_tensor"] = "" # Wrapping tabulate_tensor in a timing snippet for benchmarking if parameters["add_tabulate_tensor_timing"]: code["tabulate_tensor"] = tt_timing_template % code["tabulate_tensor"] code["additional_includes_set"] = code.get("additional_includes_set", set()) code["additional_includes_set"].add("#include <chrono>") code["additional_includes_set"].add("#include <iostream>") # Generate comment code["tabulate_tensor_comment"] = _generate_tabulate_tensor_comment(ir, parameters) return code # TODO: Replace the above with this, currently something is not working def _new_generate_integral_code(ir, parameters): "Generate code for integrals from intermediate representation." return ufc_integral(ir["integral_type"]).generate_snippets(L, ir, parameters) def _generate_finite_element_code(ir, parameters): "Generate code for finite_element from intermediate representation." return ufc_finite_element().generate_snippets(L, ir, parameters) def _generate_dofmap_code(ir, parameters): "Generate code for dofmap from intermediate representation." return ufc_dofmap().generate_snippets(L, ir, parameters) def _generate_coordinate_mapping_code(ir, parameters): "Generate code for coordinate_mapping from intermediate representation." return ufc_coordinate_mapping().generate_snippets(L, ir, parameters) def _generate_form_code(ir, parameters): "Generate code for coordinate_mapping from intermediate representation." return ufc_form().generate_snippets(L, ir, parameters)