Exemple #1
0
    def determine_keywords(self):
        # set up the restricted pointers, these are used so that the compiler
        # knows there is no aliasing in the pointers, for optimisation
        pointers = []
        # It is possible that several different variable names refer to the
        # same array. E.g. in gapjunction code, v_pre and v_post refer to the
        # same array if a group is connected to itself
        handled_pointers = set()
        template_kwds = {}
        # Again, do the import here to avoid a circular dependency.
        from brian2.devices.device import get_device
        device = get_device()
        for varname, var in self.variables.items():
            if isinstance(var, ArrayVariable):
                # This is the "true" array name, not the restricted pointer.
                array_name = device.get_array_name(var)
                pointer_name = self.get_array_name(var)
                if pointer_name in handled_pointers:
                    continue
                if getattr(var, 'ndim', 1) > 1:
                    continue  # multidimensional (dynamic) arrays have to be treated differently
                restrict = self.restrict
                # turn off restricted pointers for scalars for safety
                if var.scalar or var.size == 1:
                    restrict = ' '
                line = '{0}* {1} {2} = {3};'.format(
                    self.c_data_type(var.dtype), restrict, pointer_name,
                    array_name)
                pointers.append(line)
                handled_pointers.add(pointer_name)

        # set up the functions
        user_functions = []
        support_code = []
        hash_defines = []
        added = set()  # keep track of functions that were added
        for varname, variable in list(self.variables.items()):
            if isinstance(variable, Function):
                user_func = self._add_user_function(varname, variable, added)
                if user_func is not None:
                    hd, ps, sc, uf = user_func
                    user_functions.extend(uf)
                    support_code.extend(sc)
                    pointers.extend(ps)
                    hash_defines.extend(hd)
        support_code.append(self.universal_support_code)

        keywords = {
            'pointers_lines':
            stripped_deindented_lines('\n'.join(pointers)),
            'support_code_lines':
            stripped_deindented_lines('\n'.join(support_code)),
            'hashdefine_lines':
            stripped_deindented_lines('\n'.join(hash_defines)),
            'denormals_code_lines':
            stripped_deindented_lines('\n'.join(
                self.denormals_to_zero_code())),
        }
        keywords.update(template_kwds)
        return keywords
Exemple #2
0
    def determine_keywords(self):
        # set up the restricted pointers, these are used so that the compiler
        # knows there is no aliasing in the pointers, for optimisation
        pointers = []
        # It is possible that several different variable names refer to the
        # same array. E.g. in gapjunction code, v_pre and v_post refer to the
        # same array if a group is connected to itself
        handled_pointers = set()
        template_kwds = {}
        # Again, do the import here to avoid a circular dependency.
        from brian2.devices.device import get_device
        device = get_device()
        for varname, var in self.variables.iteritems():
            if isinstance(var, ArrayVariable):
                # This is the "true" array name, not the restricted pointer.
                array_name = device.get_array_name(var)
                pointer_name = self.get_array_name(var)
                if pointer_name in handled_pointers:
                    continue
                if getattr(var, 'dimensions', 1) > 1:
                    continue  # multidimensional (dynamic) arrays have to be treated differently
                line = '{0}* {1} {2} = {3};'.format(self.c_data_type(var.dtype),
                                                    self.restrict,
                                                    pointer_name,
                                                    array_name)
                pointers.append(line)
                handled_pointers.add(pointer_name)

        # set up the functions
        user_functions = []
        support_code = []
        hash_defines = []
        for varname, variable in self.variables.items():
            if isinstance(variable, Function):
                hd, ps, sc, uf = self._add_user_function(varname, variable)
                user_functions.extend(uf)
                support_code.extend(sc)
                pointers.extend(ps)
                hash_defines.extend(hd)


        # delete the user-defined functions from the namespace and add the
        # function namespaces (if any)
        for funcname, func in user_functions:
            del self.variables[funcname]
            func_namespace = func.implementations[self.codeobj_class].get_namespace(self.owner)
            if func_namespace is not None:
                self.variables.update(func_namespace)

        support_code.append(self.universal_support_code)


        keywords = {'pointers_lines': stripped_deindented_lines('\n'.join(pointers)),
                    'support_code_lines': stripped_deindented_lines('\n'.join(support_code)),
                    'hashdefine_lines': stripped_deindented_lines('\n'.join(hash_defines)),
                    'denormals_code_lines': stripped_deindented_lines('\n'.join(self.denormals_to_zero_code())),
                    }
        keywords.update(template_kwds)
        return keywords
    def determine_keywords(self):
        # set up the restricted pointers, these are used so that the compiler
        # knows there is no aliasing in the pointers, for optimisation
        pointers = []
        # It is possible that several different variable names refer to the
        # same array. E.g. in gapjunction code, v_pre and v_post refer to the
        # same array if a group is connected to itself
        handled_pointers = set()
        template_kwds = {}
        # Again, do the import here to avoid a circular dependency.
        from brian2.devices.device import get_device
        device = get_device()
        for varname, var in iteritems(self.variables):
            if isinstance(var, ArrayVariable):
                # This is the "true" array name, not the restricted pointer.
                array_name = device.get_array_name(var)
                pointer_name = self.get_array_name(var)
                if pointer_name in handled_pointers:
                    continue
                if get_var_ndim(var, 1) > 1:
                    continue  # multidimensional (dynamic) arrays have to be treated differently
                line = '{0}* {1} {2} = {3};'.format(self.c_data_type(var.dtype),
                                                    self.restrict,
                                                    pointer_name,
                                                    array_name)
                pointers.append(line)
                handled_pointers.add(pointer_name)

        # set up the functions
        user_functions = []
        support_code = []
        hash_defines = []
        for varname, variable in list(iteritems(self.variables)):
            if isinstance(variable, Function):
                hd, ps, sc, uf = self._add_user_function(varname, variable)
                user_functions.extend(uf)
                support_code.extend(sc)
                pointers.extend(ps)
                hash_defines.extend(hd)


        # delete the user-defined functions from the namespace and add the
        # function namespaces (if any)
        for funcname, func in user_functions:
            del self.variables[funcname]
            func_namespace = func.implementations[self.codeobj_class].get_namespace(self.owner)
            if func_namespace is not None:
                self.variables.update(func_namespace)

        support_code.append(self.universal_support_code)

        keywords = {'pointers_lines': stripped_deindented_lines('\n'.join(pointers)),
                    'support_code_lines': stripped_deindented_lines('\n'.join(support_code)),
                    'hashdefine_lines': stripped_deindented_lines('\n'.join(hash_defines)),
                    'denormals_code_lines': stripped_deindented_lines('\n'.join(self.denormals_to_zero_code())),
                    }
        keywords.update(template_kwds)
        return keywords
Exemple #4
0
 def translate_one_statement_sequence(self, statements, scalar=False):
     # This function is refactored into four functions which perform the
     # four necessary operations. It's done like this so that code
     # deriving from this class can overwrite specific parts.
     lines = []
     # index and read arrays (index arrays first)
     lines += self.translate_to_read_arrays(statements)
     # simply declare variables that will be written but not read
     lines += self.translate_to_declarations(statements)
     # the actual code
     statement_lines = self.translate_to_statements(statements)
     lines += statement_lines
     # write arrays
     lines += self.translate_to_write_arrays(statements)
     code = '\n'.join(lines)
     # Check if 64bit integer types occur in the same line as a default function.
     # We can't get the arguments of the function call directly with regex due to
     # possibly nested paranthesis inside function paranthesis.
     convertion_pref = prefs.codegen.generators.cuda.default_functions_integral_convertion
     # only check if there was no warning yet or if convertion preference has changed
     if not self.warned_integral_convertion or self.previous_convertion_pref != convertion_pref:
         for line in statement_lines:
             brian_funcs = re.search(
                 '_brian_(' + '|'.join(functions_C99) + ')', line)
             if brian_funcs is not None:
                 for identifier in get_identifiers(line):
                     if convertion_pref == 'double_precision':
                         # 64bit integer to floating-point conversions are not type safe
                         int64_type = re.search(
                             r'\bu?int64_t\s*{}\b'.format(identifier), code)
                         if int64_type is not None:
                             logger.warn(
                                 "Detected code statement with default function and 64bit integer type in the same line. "
                                 "Using 64bit integer types as default function arguments is not type safe due to convertion of "
                                 "integer to 64bit floating-point types in device code. (relevant functions: sin, cos, tan, sinh, "
                                 "cosh, tanh, exp, log, log10, sqrt, ceil, floor, arcsin, arccos, arctan)\nDetected code "
                                 "statement:\n\t{}\nGenerated from abstract code statements:\n\t{}\n"
                                 .format(line, statements),
                                 once=True)
                             self.warned_integral_convertion = True
                             self.previous_convertion_pref = 'double_precision'
                     else:  # convertion_pref = 'single_precision'
                         # 32bit and 64bit integer to floating-point conversions are not type safe
                         int32_64_type = re.search(
                             r'\bu?int(32|64)_t\s*{}\b'.format(identifier),
                             code)
                         if int32_64_type is not None:
                             logger.warn(
                                 "Detected code statement with default function and 32bit or 64bit integer type in the same line and the "
                                 "preference for default_functions_integral_convertion is 'single_precision'. "
                                 "Using 32bit or 64bit integer types as default function arguments is not type safe due to convertion of "
                                 "integer to single-precision floating-point types in device code. (relevant functions: sin, cos, tan, sinh, "
                                 "cosh, tanh, exp, log, log10, sqrt, ceil, floor, arcsin, arccos, arctan)\nDetected code "
                                 "statement:\n\t{}\nGenerated from abstract code statements:\n\t{}\n"
                                 .format(line, statements),
                                 once=True)
                             self.warned_integral_convertion = True
                             self.previous_convertion_pref = 'single_precision'
     return stripped_deindented_lines(code)
 def translate_one_statement_sequence(self, statements, scalar=False):
     if len(statements) and self.template_name=='synapses':
         vars_pre = [k for k, v in self.variable_indices.items() if v=='_presynaptic_idx']
         vars_syn = [k for k, v in self.variable_indices.items() if v=='_idx']
         vars_post = [k for k, v in self.variable_indices.items() if v=='_postsynaptic_idx']
         if '_pre_codeobject' in self.name:
             post_write_var, statements = check_pre_code(self, statements,
                                             vars_pre, vars_syn, vars_post)
             self.owner._genn_post_write_var = post_write_var
     lines = []
     lines += self.translate_to_statements(statements)
     code = '\n'.join(lines)
     return stripped_deindented_lines(code)
Exemple #6
0
 def translate_one_statement_sequence(self, statements, scalar=False):
     # This function is refactored into four functions which perform the
     # four necessary operations. It's done like this so that code
     # deriving from this class can overwrite specific parts.
     lines = []
     # index and read arrays (index arrays first)
     lines += self.translate_to_read_arrays(statements)
     # simply declare variables that will be written but not read
     lines += self.translate_to_declarations(statements)
     # the actual code
     lines += self.translate_to_statements(statements)
     # write arrays
     lines += self.translate_to_write_arrays(statements)
     code = '\n'.join(lines)                
     return stripped_deindented_lines(code)
Exemple #7
0
 def translate_one_statement_sequence(self, statements, scalar=False):
     # This function is refactored into four functions which perform the
     # four necessary operations. It's done like this so that code
     # deriving from this class can overwrite specific parts.
     lines = []
     # index and read arrays (index arrays first)
     lines += self.translate_to_read_arrays(statements)
     # simply declare variables that will be written but not read
     lines += self.translate_to_declarations(statements)
     # the actual code
     lines += self.translate_to_statements(statements)
     # write arrays
     lines += self.translate_to_write_arrays(statements)
     code = '\n'.join(lines)
     return stripped_deindented_lines(code)
 def translate_one_statement_sequence(self, statements, scalar=False):
     if len(statements) and self.template_name=='synapses':
         _, _, _, conditional_write_vars = self.arrays_helper(statements)
         vars_pre = [k for k, v in iteritems(self.variable_indices) if v=='_presynaptic_idx']
         vars_syn = [k for k, v in iteritems(self.variable_indices) if v=='_idx']
         vars_post = [k for k, v in iteritems(self.variable_indices) if v=='_postsynaptic_idx']
         if '_pre_codeobject' in self.name:
             post_write_var, statements = check_pre_code(self, statements,
                                             vars_pre, vars_syn, vars_post,
                                             conditional_write_vars)
             if (post_write_var != None):
                 self.owner._genn_post_write_var = post_write_var
     lines = []
     lines += self.translate_to_statements(statements)
     code = '\n'.join(lines)
     return stripped_deindented_lines(code)
Exemple #9
0
 def translate_one_statement_sequence(self, statements, scalar=False):
     # Note that we do not call this function from
     # `translate_statement_sequence` (which has been overwritten)
     # It is nevertheless implemented, so that it can be called explicitly
     # (e.g. from the GSL code generation)
     read, write, indices, cond_write = self.arrays_helper(statements)
     lines = []
     # index and read arrays (index arrays first)
     lines += self.translate_to_read_arrays(read, write, indices)
     # simply declare variables that will be written but not read
     lines += self.translate_to_declarations(read, write, indices)
     # the actual code
     lines += self.translate_to_statements(statements, cond_write)
     # write arrays
     lines += self.translate_to_write_arrays(write)
     return stripped_deindented_lines('\n'.join(lines))
Exemple #10
0
    def translate_statement_sequence(self, sc_statements, ve_statements):
        # This function is overwritten, since we do not want to completely
        # separate the code generation for scalar and vector code

        assert set(sc_statements.keys()) == set(ve_statements.keys())

        kwds = self.determine_keywords()

        sc_code = {}
        ve_code = {}

        for block_name in sc_statements:
            sc_block = sc_statements[block_name]
            ve_block = ve_statements[block_name]
            (sc_read, sc_write,
             sc_indices, sc_cond_write) = self.arrays_helper(sc_block)
            (ve_read, ve_write,
             ve_indices, ve_cond_write) = self.arrays_helper(ve_block)
            # We want to read all scalar variables that are needed in the
            # vector code already in the scalar code, if they are not written
            for varname in set(ve_read):
                var = self.variables[varname]
                if var.scalar and varname not in ve_write:
                    sc_read.add(varname)
                    ve_read.remove(varname)

            for (code, stmts, read, write, indices,
                 cond_write) in [(sc_code, sc_block, sc_read, sc_write,
                                  sc_indices, sc_cond_write),
                                 (ve_code, ve_block, ve_read, ve_write,
                                  ve_indices, ve_cond_write)]:
                lines = []
                # index and read arrays (index arrays first)
                lines += self.translate_to_read_arrays(read, write, indices)
                # simply declare variables that will be written but not read
                lines += self.translate_to_declarations(read, write, indices)
                # the actual code
                lines += self.translate_to_statements(stmts, cond_write)
                # write arrays
                lines += self.translate_to_write_arrays(write)
                code[block_name] = stripped_deindented_lines('\n'.join(lines))

        return sc_code, ve_code, kwds
Exemple #11
0
    def translate_one_statement_sequence(self, statements, variables,
                                         variable_indices, iterate_all,
                                         codeobj_class):

        # Note that C++ code does not care about the iterate_all argument -- it
        # always has to loop over the elements

        read, write, indices = self.array_read_write(statements, variables,
                                                     variable_indices)
        lines = []
        # index and read arrays (index arrays first)
        for varname in itertools.chain(indices, read):
            index_var = variable_indices[varname]
            var = variables[varname]
            if varname not in write:
                line = 'const '
            else:
                line = ''
            line = line + self.c_data_type(var.dtype) + ' ' + varname + ' = '
            line = line + self.get_array_name(var, variables) + '[' + index_var + '];'
            lines.append(line)
        # simply declare variables that will be written but not read
        for varname in write:
            if varname not in read:
                var = variables[varname]
                line = self.c_data_type(var.dtype) + ' ' + varname + ';'
                lines.append(line)
        # the actual code
        lines.extend([self.translate_statement(stmt, variables, codeobj_class)
                      for stmt in statements])
        # write arrays
        for varname in write:
            index_var = variable_indices[varname]
            var = variables[varname]
            line = self.get_array_name(var, variables) + '[' + index_var + '] = ' + varname + ';'
            lines.append(line)
        code = '\n'.join(lines)
                
        return stripped_deindented_lines(code)
Exemple #12
0
    def translate_statement_sequence(self, statements, variables, namespace,
                                     variable_indices, iterate_all):

        # Note that C++ code does not care about the iterate_all argument -- it
        # always has to loop over the elements

        read, write = self.array_read_write(statements, variables)
        lines = []
        # read arrays
        for varname in read:
            index_var = variable_indices[varname]
            var = variables[varname]
            if varname not in write:
                line = 'const '
            else:
                line = ''
            line = line + self.c_data_type(var.dtype) + ' ' + varname + ' = '
            line = line + '_ptr' + var.arrayname + '[' + index_var + '];'
            lines.append(line)
        # simply declare variables that will be written but not read
        for varname in write:
            if varname not in read:
                var = variables[varname]
                line = self.c_data_type(var.dtype) + ' ' + varname + ';'
                lines.append(line)
        # the actual code
        lines.extend([self.translate_statement(stmt) for stmt in statements])
        # write arrays
        for varname in write:
            index_var = variable_indices[varname]
            var = variables[varname]
            line = '_ptr' + var.arrayname + '[' + index_var + '] = ' + varname + ';'
            lines.append(line)
        code = '\n'.join(lines)
        # set up the restricted pointers, these are used so that the compiler
        # knows there is no aliasing in the pointers, for optimisation
        lines = []
        # It is possible that several different variable names refer to the
        # same array. E.g. in gapjunction code, v_pre and v_post refer to the
        # same array if a group is connected to itself
        arraynames = set()
        for varname, var in variables.iteritems():
            if isinstance(var, ArrayVariable):
                arrayname = var.arrayname
                if not arrayname in arraynames:
                    line = self.c_data_type(
                        var.dtype
                    ) + ' * ' + self.restrict + '_ptr' + arrayname + ' = ' + arrayname + ';'
                    lines.append(line)
                    arraynames.add(arrayname)
        pointers = '\n'.join(lines)

        # set up the functions
        user_functions = []
        support_code = ''
        hash_defines = ''
        for varname, variable in namespace.items():
            if isinstance(variable, Function):
                user_functions.append(varname)
                speccode = variable.code(self, varname)
                support_code += '\n' + deindent(speccode['support_code'])
                hash_defines += deindent(speccode['hashdefine_code'])
                # add the Python function with a leading '_python', if it
                # exists. This allows the function to make use of the Python
                # function via weave if necessary (e.g. in the case of randn)
                if not variable.pyfunc is None:
                    pyfunc_name = '_python_' + varname
                    if pyfunc_name in namespace:
                        logger.warn(('Namespace already contains function %s, '
                                     'not replacing it') % pyfunc_name)
                    else:
                        namespace[pyfunc_name] = variable.pyfunc

        # delete the user-defined functions from the namespace
        for func in user_functions:
            del namespace[func]

        # return
        return (stripped_deindented_lines(code), {
            'pointers_lines':
            stripped_deindented_lines(pointers),
            'support_code_lines':
            stripped_deindented_lines(support_code),
            'hashdefine_lines':
            stripped_deindented_lines(hash_defines),
            'denormals_code_lines':
            stripped_deindented_lines(self.denormals_to_zero_code()),
        })
Exemple #13
0
    def translate_statement_sequence(self, statements, variables, namespace,
                                     variable_indices, iterate_all):

        # Note that C++ code does not care about the iterate_all argument -- it
        # always has to loop over the elements

        read, write = self.array_read_write(statements, variables)
        lines = []
        # read arrays
        for varname in read:
            index_var = variable_indices[varname]
            var = variables[varname]
            if varname not in write:
                line = 'const '
            else:
                line = ''
            line = line + self.c_data_type(var.dtype) + ' ' + varname + ' = '
            line = line + '_ptr' + var.arrayname + '[' + index_var + '];'
            lines.append(line)
        # simply declare variables that will be written but not read
        for varname in write:
            if varname not in read:
                var = variables[varname]
                line = self.c_data_type(var.dtype) + ' ' + varname + ';'
                lines.append(line)
        # the actual code
        lines.extend([self.translate_statement(stmt) for stmt in statements])
        # write arrays
        for varname in write:
            index_var = variable_indices[varname]
            var = variables[varname]
            line = '_ptr' + var.arrayname + '[' + index_var + '] = ' + varname + ';'
            lines.append(line)
        code = '\n'.join(lines)
        # set up the restricted pointers, these are used so that the compiler
        # knows there is no aliasing in the pointers, for optimisation
        lines = []
        # It is possible that several different variable names refer to the
        # same array. E.g. in gapjunction code, v_pre and v_post refer to the
        # same array if a group is connected to itself
        arraynames = set()
        for varname, var in variables.iteritems():
            if isinstance(var, ArrayVariable):
                arrayname = var.arrayname
                if not arrayname in arraynames:
                    line = self.c_data_type(var.dtype) + ' * ' + self.restrict + '_ptr' + arrayname + ' = ' + arrayname + ';'
                    lines.append(line)
                    arraynames.add(arrayname)
        pointers = '\n'.join(lines)
        
        # set up the functions
        user_functions = []
        support_code = ''
        hash_defines = ''
        for varname, variable in namespace.items():
            if isinstance(variable, Function):
                user_functions.append(varname)
                speccode = variable.code(self, varname)
                support_code += '\n' + deindent(speccode['support_code'])
                hash_defines += deindent(speccode['hashdefine_code'])
                # add the Python function with a leading '_python', if it
                # exists. This allows the function to make use of the Python
                # function via weave if necessary (e.g. in the case of randn)
                if not variable.pyfunc is None:
                    pyfunc_name = '_python_' + varname
                    if pyfunc_name in namespace:
                        logger.warn(('Namespace already contains function %s, '
                                     'not replacing it') % pyfunc_name)
                    else:
                        namespace[pyfunc_name] = variable.pyfunc
        
        # delete the user-defined functions from the namespace
        for func in user_functions:
            del namespace[func]
        
        # return
        return (stripped_deindented_lines(code),
                {'pointers_lines': stripped_deindented_lines(pointers),
                 'support_code_lines': stripped_deindented_lines(support_code),
                 'hashdefine_lines': stripped_deindented_lines(hash_defines),
                 'denormals_code_lines': stripped_deindented_lines(self.denormals_to_zero_code()),
                 })
    def determine_keywords(self):
        # set up the restricted pointers, these are used so that the compiler
        # knows there is no aliasing in the pointers, for optimisation
        lines = []
        # It is possible that several different variable names refer to the
        # same array. E.g. in gapjunction code, v_pre and v_post refer to the
        # same array if a group is connected to itself
        handled_pointers = set()
        template_kwds = {}
        # Again, do the import here to avoid a circular dependency.
        from brian2.devices.device import get_device
        device = get_device()
        for varname, var in self.variables.iteritems():
            if isinstance(var, ArrayVariable):
                # This is the "true" array name, not the restricted pointer.
                array_name = device.get_array_name(var)
                pointer_name = self.get_array_name(var)
                if pointer_name in handled_pointers:
                    continue
                if getattr(var, 'dimensions', 1) > 1:
                    continue  # multidimensional (dynamic) arrays have to be treated differently
                line = self.c_data_type(var.dtype) + ' * ' + self.restrict + pointer_name + ' = ' + array_name + ';'
                lines.append(line)
                handled_pointers.add(pointer_name)

        pointers = '\n'.join(lines)

        # set up the functions
        user_functions = []
        support_code = ''
        hash_defines = ''
        for varname, variable in self.variables.items():
            if isinstance(variable, Function):
                user_functions.append((varname, variable))
                funccode = variable.implementations[self.codeobj_class].get_code(self.owner)
                if isinstance(funccode, basestring):
                    funccode = {'support_code': funccode}
                if funccode is not None:
                    support_code += '\n' + deindent(funccode.get('support_code', ''))
                    hash_defines += '\n' + deindent(funccode.get('hashdefine_code', ''))
                # add the Python function with a leading '_python', if it
                # exists. This allows the function to make use of the Python
                # function via weave if necessary (e.g. in the case of randn)
                if not variable.pyfunc is None:
                    pyfunc_name = '_python_' + varname
                    if pyfunc_name in self.variables:
                        logger.warn(('Namespace already contains function %s, '
                                     'not replacing it') % pyfunc_name)
                    else:
                        self.variables[pyfunc_name] = variable.pyfunc

        # delete the user-defined functions from the namespace and add the
        # function namespaces (if any)
        for funcname, func in user_functions:
            del self.variables[funcname]
            func_namespace = func.implementations[self.codeobj_class].get_namespace(self.owner)
            if func_namespace is not None:
                self.variables.update(func_namespace)

        keywords = {'pointers_lines': stripped_deindented_lines(pointers),
                    'support_code_lines': stripped_deindented_lines(support_code),
                    'hashdefine_lines': stripped_deindented_lines(hash_defines),
                    'denormals_code_lines': stripped_deindented_lines(self.denormals_to_zero_code()),
                    }
        keywords.update(template_kwds)
        return keywords
Exemple #15
0
    def determine_keywords(self):
        # set up the restricted pointers, these are used so that the compiler
        # knows there is no aliasing in the pointers, for optimisation
        lines = []
        # it is possible that several different variable names refer to the
        # same array. E.g. in gapjunction code, v_pre and v_post refer to the
        # same array if a group is connected to itself
        handled_pointers = set()
        template_kwds = {}
        # again, do the import here to avoid a circular dependency.
        from brian2.devices.device import get_device
        device = get_device()
        for varname, var in self.variables.iteritems():
            if isinstance(var, ArrayVariable):
                # This is the "true" array name, not the restricted pointer.
                array_name = device.get_array_name(var)
                pointer_name = self.get_array_name(var)
                if pointer_name in handled_pointers:
                    continue
                if getattr(var, 'ndim', 1) > 1:
                    continue  # multidimensional (dynamic) arrays have to be treated differently
                line = self.c_data_type(var.dtype) + ' * ' + self.restrict + pointer_name + ' = ' + array_name + ';'
                lines.append(line)
                handled_pointers.add(pointer_name)

        pointers = '\n'.join(lines)

        # set up the functions
        user_functions = []
        support_code = ''
        hash_defines = ''
        # set convertion types for standard C99 functions in device code
        if prefs.codegen.generators.cuda.default_functions_integral_convertion == np.float64:
            default_func_type = 'double'
            other_func_type = 'float'
        else:  # np.float32
            default_func_type = 'float'
            other_func_type = 'double'
        # set clip function to either use all float or all double arguments
        # see #51 for details
        if prefs['core.default_float_dtype'] == np.float64:
            float_dtype = 'float'
        else:  # np.float32
            float_dtype = 'double'
        for varname, variable in self.variables.items():
            if isinstance(variable, Function):
                user_functions.append((varname, variable))
                funccode = variable.implementations[self.codeobj_class].get_code(self.owner)
                if varname in functions_C99:
                    funccode = funccode.format(default_type=default_func_type, other_type=other_func_type)
                if varname == 'clip':
                    funccode = funccode.format(float_dtype=float_dtype)
                if isinstance(funccode, basestring):
                    funccode = {'support_code': funccode}
                if funccode is not None:
                    support_code += '\n' + deindent(funccode.get('support_code', ''))
                    hash_defines += '\n' + deindent(funccode.get('hashdefine_code', ''))
                # add the Python function with a leading '_python', if it
                # exists. This allows the function to make use of the Python
                # function via weave if necessary (e.g. in the case of randn)
                if not variable.pyfunc is None:
                    pyfunc_name = '_python_' + varname
                    if pyfunc_name in self.variables:
                        logger.warn(('Namespace already contains function %s, '
                                     'not replacing it') % pyfunc_name)
                    else:
                        self.variables[pyfunc_name] = variable.pyfunc

        # delete the user-defined functions from the namespace and add the
        # function namespaces (if any)
        for funcname, func in user_functions:
            del self.variables[funcname]
            func_namespace = func.implementations[self.codeobj_class].get_namespace(self.owner)
            if func_namespace is not None:
                self.variables.update(func_namespace)

        support_code += '\n' + deindent(self.universal_support_code)

        keywords = {'pointers_lines': stripped_deindented_lines(pointers),
                    'support_code_lines': stripped_deindented_lines(support_code),
                    'hashdefine_lines': stripped_deindented_lines(hash_defines),
                    'denormals_code_lines': stripped_deindented_lines(self.denormals_to_zero_code()),
                    'uses_atomics': self.uses_atomics
                    }
        keywords.update(template_kwds)
        return keywords
    def translate_one_statement_sequence(self, statements, scalar=False):
        # This function is refactored into four functions which perform the
        # four necessary operations. It's done like this so that code
        # deriving from this class can overwrite specific parts.
        all_unique = not self.has_repeated_indices(statements)

        read, write, indices, conditional_write_vars = self.arrays_helper(statements)
        try:
            # try to use atomics
            if not self._use_atomics or scalar or all_unique:
                raise ParallelisationError()
            # more complex translations to deal with repeated indices, which
            # could lead to race conditions when applied in parallel
            lines = self.parallelise_code(statements)
            self.uses_atomics = True
        except ParallelisationError:
            # don't use atomics
            lines = []
            # index and read arrays (index arrays first)
            lines += self.translate_to_read_arrays(read, write, indices)
            # simply declare variables that will be written but not read
            lines += self.translate_to_declarations(read, write, indices)
            # the actual code
            lines += self.translate_to_statements(statements,
                                                  conditional_write_vars)
            # write arrays
            lines += self.translate_to_write_arrays(write)
        code = '\n'.join(lines)

        # Check if 64bit integer types occur in the same line as a default function.
        # We can't get the arguments of the function call directly with regex due to
        # possibly nested paranthesis inside function paranthesis.
        statement_lines = self.translate_to_statements(statements,
                                                       conditional_write_vars)
        convertion_pref = prefs.devices.cuda_standalone.default_functions_integral_convertion
        # only check if there was no warning yet or if convertion preference has changed
        if not self.warned_integral_convertion or self.previous_convertion_pref != convertion_pref:
            for line in statement_lines:
                brian_funcs = re.search('_brian_(' + '|'.join(functions_C99) + ')', line)
                if brian_funcs is not None:
                    for identifier in get_identifiers(line):
                        if convertion_pref == np.float64:
                            # 64bit integer to floating-point conversions are not type safe
                            int64_type = re.search(r'\bu?int64_t\s*{}\b'.format(identifier), code)
                            if int64_type is not None:
                                logger.warn("Detected code statement with default function and 64bit integer type in the same line. "
                                            "Using 64bit integer types as default function arguments is not type safe due to convertion of "
                                            "integer to 64bit floating-point types in device code. (relevant functions: {})\nDetected code "
                                            "statement:\n\t{}\nGenerated from abstract code statements:\n\t{}\n".format(
                                                ', '.join(functions_C99), line, statements
                                            ),
                                            once=True)
                                self.warned_integral_convertion = True
                                self.previous_convertion_pref = np.float64
                        else:  # convertion_pref = np.float32
                            # 32bit and 64bit integer to floating-point conversions are not type safe
                            int32_64_type = re.search(r'\bu?int(32|64)_t\s*{}\b'.format(identifier), code)
                            if int32_64_type is not None:
                                logger.warn("Detected code statement with default function and 32bit or 64bit integer type in the same line and the "
                                            "preference for default_functions_integral_convertion is 'float32'. "
                                            "Using 32bit or 64bit integer types as default function arguments is not type safe due to convertion of "
                                            "integer to single-precision floating-point types in device code. (relevant functions: {})\nDetected code "
                                            "statement:\n\t{}\nGenerated from abstract code statements:\n\t{}\n".format(
                                                ', '.join(functions_C99), line, statements
                                            ),
                                            once=True)
                                self.warned_integral_convertion = True
                                self.previous_convertion_pref = np.float32
        return stripped_deindented_lines(code)
    def determine_keywords(self):
        # set up the restricted pointers, these are used so that the compiler
        # knows there is no aliasing in the pointers, for optimisation
        pointers = []
        # Add additional lines inside the kernel functions
        kernel_lines = []
        # It is possible that several different variable names refer to the
        # same array. E.g. in gapjunction code, v_pre and v_post refer to the
        # same array if a group is connected to itself
        handled_pointers = set()
        template_kwds = {}
        # Again, do the import here to avoid a circular dependency.
        from brian2.devices.device import get_device
        device = get_device()
        for varname, var in self.variables.iteritems():
            if isinstance(var, ArrayVariable):
                # This is the "true" array name, not the restricted pointer.
                array_name = device.get_array_name(var)
                pointer_name = self.get_array_name(var)
                if pointer_name in handled_pointers:
                    continue
                if getattr(var, 'ndim', 1) > 1:
                    continue  # multidimensional (dynamic) arrays have to be treated differently
                restrict = self.restrict
                # turn off restricted pointers for scalars for safety
                if var.scalar:
                    restrict = ' '
                line = '{0}* {1} {2} = {3};'.format(
                    self.c_data_type(var.dtype), restrict, pointer_name,
                    array_name)
                pointers.append(line)
                handled_pointers.add(pointer_name)

        # set up the functions
        user_functions = []
        support_code = []
        hash_defines = []
        for varname, variable in self.variables.items():
            if isinstance(variable, Function):
                hd, ps, sc, uf, kl = self._add_user_function(varname, variable)
                user_functions.extend(uf)
                support_code.extend(sc)
                pointers.extend(ps)
                hash_defines.extend(hd)
                kernel_lines.extend(kl)
        support_code.append(self.universal_support_code)

        # Clock variables (t, dt, timestep) are passed by value to kernels and
        # need to be translated back into pointers for scalar/vector code.
        for varname, variable in self.variables.iteritems():
            if hasattr(variable, 'owner') and isinstance(
                    variable.owner, Clock):
                # get arrayname without _ptr suffix (e.g. _array_defaultclock_dt)
                arrayname = self.get_array_name(variable, pointer=False)
                line = "const {dtype}* _ptr{arrayname} = &_value{arrayname};"
                line = line.format(dtype=c_data_type(variable.dtype),
                                   arrayname=arrayname)
                if line not in kernel_lines:
                    kernel_lines.append(line)

        keywords = {
            'pointers_lines':
            stripped_deindented_lines('\n'.join(pointers)),
            'support_code_lines':
            stripped_deindented_lines('\n'.join(support_code)),
            'hashdefine_lines':
            stripped_deindented_lines('\n'.join(hash_defines)),
            'denormals_code_lines':
            stripped_deindented_lines('\n'.join(
                self.denormals_to_zero_code())),
            'kernel_lines':
            stripped_deindented_lines('\n'.join(kernel_lines)),
            'uses_atomics':
            self.uses_atomics
        }
        keywords.update(template_kwds)
        return keywords
    def determine_keywords(self):
        # set up the restricted pointers, these are used so that the compiler
        # knows there is no aliasing in the pointers, for optimisation
        pointers = []
        # Add additional lines inside the kernel functions
        kernel_lines = []
        # It is possible that several different variable names refer to the
        # same array. E.g. in gapjunction code, v_pre and v_post refer to the
        # same array if a group is connected to itself
        handled_pointers = set()
        template_kwds = {}
        # Again, do the import here to avoid a circular dependency.
        from brian2.devices.device import get_device
        device = get_device()
        for varname, var in self.variables.items():
            if isinstance(var, ArrayVariable):
                # This is the "true" array name, not the restricted pointer.
                array_name = device.get_array_name(var)
                pointer_name = self.get_array_name(var)
                if pointer_name in handled_pointers:
                    continue
                if getattr(var, 'ndim', 1) > 1:
                    continue  # multidimensional (dynamic) arrays have to be treated differently
                restrict = self.restrict
                # turn off restricted pointers for scalars for safety
                if var.scalar:
                    restrict = ' '
                # Need to use correct dt type in pointers_lines for single precision,
                # see #148
                if varname == "dt" and prefs.core.default_float_dtype == np.float32:
                    # c_data_type(variable.dtype) is float, but we need double
                    dtype = "double"
                else:
                    dtype = self.c_data_type(var.dtype)
                line = '{0}* {1} {2} = {3};'.format(dtype,
                                                    restrict,
                                                    pointer_name,
                                                    array_name)
                pointers.append(line)
                handled_pointers.add(pointer_name)

        # set up the functions
        user_functions = []
        support_code = []
        hash_defines = []
        added = set()  # keep track of functions that were added
        for varname, variable in list(self.variables.items()):
            if isinstance(variable, Function):
                user_func = self._add_user_function(varname, variable, added)
                if user_func is not None:
                    hd, ps, sc, uf, kl = user_func
                    user_functions.extend(uf)
                    support_code.extend(sc)
                    pointers.extend(ps)
                    hash_defines.extend(hd)
                    kernel_lines.extend(kl)

        # Generate universal_support_code once when the first codeobject is created.
        # Can't do it at import time since need access to user preferences
        # This is a class attribute (not instance attribute).
        if CUDACodeGenerator.universal_support_code is None:
            _atomic_support_code = _generate_atomic_support_code()
            CUDACodeGenerator.universal_support_code = (
                _hightype_support_code
                + _mod_support_code
                + _floordiv_support_code
                + _pow_support_code
                + _atomic_support_code
            )
        support_code.append(CUDACodeGenerator.universal_support_code)

        # Clock variables (t, dt, timestep) are passed by value to kernels and
        # need to be translated back into pointers for scalar/vector code.
        for varname, variable in self.variables.items():
            if hasattr(variable, 'owner') and isinstance(variable.owner, Clock):
                # get arrayname without _ptr suffix (e.g. _array_defaultclock_dt)
                arrayname = self.get_array_name(variable, prefix='')
                # kernel_lines appear before dt is cast to float (in scalar_code), hence
                # we need to still use double (used in kernel parameters), see #148
                if varname == "dt" and prefs.core.default_float_dtype == np.float32:
                    # c_data_type(variable.dtype) is float, but we need double
                    dtype = "double"
                else:
                    dtype = dtype=c_data_type(variable.dtype)
                line = f"const {dtype}* _ptr{arrayname} = &_value{arrayname};"
                if line not in kernel_lines:
                    kernel_lines.append(line)

        keywords = {'pointers_lines': stripped_deindented_lines('\n'.join(pointers)),
                    'support_code_lines': stripped_deindented_lines('\n'.join(support_code)),
                    'hashdefine_lines': stripped_deindented_lines('\n'.join(hash_defines)),
                    'denormals_code_lines': stripped_deindented_lines('\n'.join(self.denormals_to_zero_code())),
                    'kernel_lines': stripped_deindented_lines('\n'.join(kernel_lines)),
                    'uses_atomics': self.uses_atomics
                    }
        keywords.update(template_kwds)
        return keywords