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
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
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)
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): # 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)
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))
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
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)
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 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
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