def get_array_name(var, access_data=True, prefix=None): ''' Return a globally unique name for `var`. See CUDAStandaloneDevice.get_array_name for parameters. Here, `prefix` defaults to `'_ptr'` when `access_data=True`. `prefix='_ptr'` is used since the CUDACodeGenerator generates the `scalar_code` and `vector_code` snippets. ''' # We have to do the import here to avoid circular import dependencies. from brian2.devices.device import get_device device = get_device() if access_data: if prefix is None: prefix = '_ptr' return device.get_array_name(var, access_data=True, prefix=prefix) else: return device.get_array_name(var, access_data=False, prefix=prefix)
def pytest_runtest_makereport(item, call): if hasattr(item.config, 'workerinput'): fail_for_not_implemented = item.config.workerinput['fail_for_not_implemented'] else: fail_for_not_implemented = item.config.fail_for_not_implemented outcome = yield rep = outcome.get_result() if rep.outcome == 'failed': project_dir = getattr(get_device(), 'project_dir', None) if project_dir is not None: rep.sections.append(('Standalone project directory', f'{project_dir}')) reinit_devices() if not fail_for_not_implemented: exc_cause = getattr(call.excinfo.value, '__cause__', None) if (call.excinfo.errisinstance(NotImplementedError) or isinstance(exc_cause, NotImplementedError)): rep.outcome = 'skipped' r = call.excinfo._getreprcrash() rep.longrepr = (str(r.path), r.lineno, r.message) else: # clean up after the test (delete directory for standalone) reinit_and_delete()
def create_runner_codeobj(group, code, template_name, variable_indices=None, name=None, check_units=True, needed_variables=None, additional_variables=None, additional_namespace=None, template_kwds=None): ''' Create a `CodeObject` for the execution of code in the context of a `Group`. Parameters ---------- group : `Group` The group where the code is to be run code : str The code to be executed. template_name : str The name of the template to use for the code. variable_indices : dict-like, optional A mapping from `Variable` objects to index names (strings). If none is given, uses the corresponding attribute of `group`. name : str, optional A name for this code object, will use ``group + '_codeobject*'`` if none is given. check_units : bool, optional Whether to check units in the statement. Defaults to ``True``. needed_variables: list of str, optional A list of variables that are neither present in the abstract code, nor in the ``USES_VARIABLES`` statement in the template. This is only rarely necessary, an example being a `StateMonitor` where the names of the variables are neither known to the template nor included in the abstract code statements. additional_variables : dict-like, optional A mapping of names to `Variable` objects, used in addition to the variables saved in `group`. additional_namespace : dict-like, optional A mapping from names to objects, used in addition to the namespace saved in `group`. template_kwds : dict, optional A dictionary of additional information that is passed to the template. ''' logger.debug('Creating code object for abstract code:\n' + str(code)) from brian2.devices import get_device device = get_device() if check_units: if isinstance(code, dict): for c in code.values(): check_code_units(c, group, additional_variables=additional_variables, additional_namespace=additional_namespace) else: check_code_units(code, group, additional_variables=additional_variables, additional_namespace=additional_namespace) codeobj_class = device.code_object_class(group.codeobj_class) template = getattr(codeobj_class.templater, template_name) all_variables = dict(group.variables) if additional_variables is not None: all_variables.update(additional_variables) # Determine the identifiers that were used if isinstance(code, dict): used_known = set() unknown = set() for v in code.values(): _, uk, u = analyse_identifiers(v, all_variables, recursive=True) used_known |= uk unknown |= u else: _, used_known, unknown = analyse_identifiers(code, all_variables, recursive=True) logger.debug('Unknown identifiers in the abstract code: ' + str(unknown)) # Only pass the variables that are actually used variables = {} for var in used_known: # Emit a warning if a variable is also present in the namespace if (var in group.namespace or (additional_namespace is not None and var in additional_namespace[1])): message = ('Variable {var} is present in the namespace but is also an' ' internal variable of {name}, the internal variable will' ' be used.'.format(var=var, name=group.name)) logger.warn(message, 'create_runner_codeobj.resolution_conflict', once=True) variables[var] = all_variables[var] resolved_namespace = group.namespace.resolve_all(unknown, additional_namespace) for varname, value in resolved_namespace.iteritems(): if isinstance(value, Function): variables[varname] = value else: unit = get_unit(value) # For the moment, only allow the use of scalar values array_value = np.asarray(value) if array_value.shape != (): raise TypeError('Name "%s" does not refer to a scalar value' % varname) variables[varname] = Constant(name=varname, unit=unit, value=value) # Add variables that are not in the abstract code, nor specified in the # template but nevertheless necessary if needed_variables is None: needed_variables = [] for var in needed_variables: variables[var] = all_variables[var] # Also add the variables that the template needs for var in template.variables: try: variables[var] = all_variables[var] except KeyError as ex: # We abuse template.variables here to also store names of things # from the namespace (e.g. rand) that are needed # TODO: Improve all of this namespace/specifier handling if group is not None: # Try to find the name in the group's namespace variables[var] = group.namespace.resolve(var, additional_namespace) else: raise ex # always add N, the number of neurons or synapses variables['N'] = all_variables['N'] if name is None: if group is not None: name = '%s_%s_codeobject*' % (group.name, template_name) else: name = '%s_codeobject*' % template_name all_variable_indices = copy.copy(group.variables.indices) if additional_variables is not None: all_variable_indices.update(additional_variables.indices) if variable_indices is not None: all_variable_indices.update(variable_indices) # Add the indices needed by the variables varnames = variables.keys() for varname in varnames: var_index = all_variable_indices[varname] if var_index != '_idx': variables[var_index] = all_variables[var_index] return device.code_object(owner=group, name=name, abstract_code=code, variables=variables, template_name=template_name, variable_indices=all_variable_indices, template_kwds=template_kwds, codeobj_class=group.codeobj_class)
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
def _add_user_function(self, varname, variable, added): impl = variable.implementations[self.codeobj_class] if (impl.name, variable) in added: return # nothing to do else: added.add((impl.name, variable)) support_code = [] hash_defines = [] pointers = [] kernel_lines = [] user_functions = [(varname, variable)] funccode = impl.get_code(self.owner) if isinstance(funccode, str): # Rename references to any dependencies if necessary for dep_name, dep in impl.dependencies.items(): dep_impl = dep.implementations[self.codeobj_class] dep_impl_name = dep_impl.name if dep_impl_name is None: dep_impl_name = dep.pyfunc.__name__ if dep_name != dep_impl_name: funccode = word_substitute(funccode, {dep_name: dep_impl_name}) ### Different from CPPCodeGenerator: We format the funccode dtypes here from brian2.devices.device import get_device device = get_device() if varname in functions_C99: funccode = funccode.format(default_type=self.default_func_type, other_type=self.other_func_type) elif varname in ['clip', 'exprel']: funccode = funccode.format(float_dtype=self.float_dtype) ### if isinstance(funccode, str): funccode = {'support_code': funccode} if funccode is not None: # To make namespace variables available to functions, we # create global variables and assign to them in the main # code func_namespace = impl.get_namespace(self.owner) or {} for ns_key, ns_value in func_namespace.items(): # This section is adapted from CPPCodeGenerator such that file # global namespace pointers can be used in both host and device # code. assert hasattr(ns_value, 'dtype'), \ 'This should not have happened. Please report at ' \ 'https://github.com/brian-team/brian2cuda/issues/new' if ns_value.shape == (): raise NotImplementedError(( 'Directly replace scalar values in the function ' 'instead of providing them via the namespace')) type_str = self.c_data_type(ns_value.dtype) + '*' namespace_ptr = ''' #if (defined(__CUDA_ARCH__) && (__CUDA_ARCH__ > 0)) __device__ {dtype} _namespace{name}; #else {dtype} _namespace{name}; #endif '''.format(dtype=type_str, name=ns_key) support_code.append(namespace_ptr) # pointer lines will be used in codeobjects running on the host pointers.append('_namespace{name} = {name};'.format(name=ns_key)) # kernel lines will be used in codeobjects running on the device kernel_lines.append(''' #if (defined(__CUDA_ARCH__) && (__CUDA_ARCH__ > 0)) _namespace{name} = d{name}; #else _namespace{name} = {name}; #endif '''.format(name=ns_key)) support_code.append(deindent(funccode.get('support_code', ''))) hash_defines.append(deindent(funccode.get('hashdefine_code', ''))) dep_hash_defines = [] dep_pointers = [] dep_support_code = [] dep_kernel_lines = [] if impl.dependencies is not None: for dep_name, dep in impl.dependencies.items(): if dep_name not in self.variables: self.variables[dep_name] = dep dep_impl = dep.implementations[self.codeobj_class] if dep_name != dep_impl.name: self.func_name_replacements[dep_name] = dep_impl.name user_function = self._add_user_function(dep_name, dep, added) if user_function is not None: hd, ps, sc, uf, kl = user_function dep_hash_defines.extend(hd) dep_pointers.extend(ps) dep_support_code.extend(sc) user_functions.extend(uf) dep_kernel_lines.extend(kl) return (dep_hash_defines + hash_defines, dep_pointers + pointers, dep_support_code + support_code, user_functions, dep_kernel_lines + kernel_lines)
def _generate_atomic_support_code(): # (arg_dtype, int_dtype, name in type cast function) overloads = [('int', 'int', 'int'), ('float', 'int', 'int'), ('double', 'unsigned long long int', 'longlong')] cuda_runtime_version = get_cuda_runtime_version() # Note: There are atomic functions that are supported only for compute capability >= # 3.5. We don't check for those as we require at least 3.5. If we ever support # smaller CC, we need to adapt the code here (see Atomic Functions in CUDA # Programming Guide) device = get_device() assert device.minimal_compute_capability >= 3.5, "Need to adapt atomic support code" software_implementation_float_dtype = ''' // software implementation {int_dtype}* address_as_int = ({int_dtype}*)address; {int_dtype} old = *address_as_int, assumed; do {{ assumed = old; old = atomicCAS(address_as_int, assumed, __{arg_dtype}_as_{val_type_cast}(val {op} __{val_type_cast}_as_{arg_dtype}(assumed))); // Note: uses integer comparison to avoid hang in case of NaN (since NaN != NaN) }} while (assumed != old); return __{val_type_cast}_as_{arg_dtype}(old); ''' hardware_implementation = ''' // hardware implementation return atomic{op_name}(address, val); ''' atomic_support_code = '' for op_name, op in [('Add', '+'), ('Mul', '*'), ('Div', '/')]: for arg_dtype, int_dtype, val_type_cast in overloads: # atomicAdd had hardware implementations, depending on dtype, compute # capability and CUDA runtime version. This section uses them when possible. if op_name == 'Add': format_sw = True code = ''' inline __device__ {arg_dtype} _brian_atomic{op_name}({arg_dtype}* address, {arg_dtype} val) {{ ''' if arg_dtype in ['int', 'float']: code += f''' {hardware_implementation} ''' elif arg_dtype == 'double': if cuda_runtime_version >= 8.0: # Check for CC in at runtime to use software or hardware # implementation. # Don't need to check for defined __CUDA__ARCH__, it is always defined # in __device__ and __global__ functions (no host code path). code += ''' #if (__CUDA_ARCH__ >= 600) {hardware_implementation} #else {software_implementation} #endif '''.format( hardware_implementation=hardware_implementation, software_implementation=software_implementation_float_dtype ) else: # For runtime < 8.0, there is no atomicAdd hardware implementation # support independent of CC. code += ''' {software_implementation} '''.format( software_implementation=software_implementation_float_dtype ) format_sw = True code += ''' }} ''' if format_sw: code = code.format( arg_dtype=arg_dtype, int_dtype=int_dtype, val_type_cast=val_type_cast, op_name=op_name, op=op ) else: code = code.format(arg_dtype=arg_dtype, op_name=op_name) atomic_support_code += code # For other atomic operations on int types, we can use the same template. elif arg_dtype == 'int': atomic_support_code += ''' inline __device__ int _brian_atomic{op_name}(int* address, int val) {{ // software implementation int old = *address, assumed; do {{ assumed = old; old = atomicCAS(address, assumed, val {op} assumed); }} while (assumed != old); return old; }} '''.format(op_name=op_name, op=op) # Else (not atomicAdd and not int types) use this template. else: # in the software implementation, we treat all data as integer types # (since atomicCAS is only define # and use atomicCAS to swap the memory with our our desired value code = ''' inline __device__ {{arg_dtype}} _brian_atomic{{op_name}}({{arg_dtype}}* address, {{arg_dtype}} val) {{{{ {software_implementation} }}}} '''.format(software_implementation=software_implementation_float_dtype) # above, just add the software_implementation code, below add the other # format variables (also present in software_implementation) code = code.format( arg_dtype=arg_dtype, int_dtype=int_dtype, val_type_cast=val_type_cast, op_name=op_name, op=op, ) atomic_support_code += code return atomic_support_code
def create_runner_codeobj( group, code, template_name, variable_indices=None, name=None, check_units=True, needed_variables=None, additional_variables=None, level=0, run_namespace=None, template_kwds=None, override_conditional_write=None, ): ''' Create a `CodeObject` for the execution of code in the context of a `Group`. Parameters ---------- group : `Group` The group where the code is to be run code : str or dict of str The code to be executed. template_name : str The name of the template to use for the code. variable_indices : dict-like, optional A mapping from `Variable` objects to index names (strings). If none is given, uses the corresponding attribute of `group`. name : str, optional A name for this code object, will use ``group + '_codeobject*'`` if none is given. check_units : bool, optional Whether to check units in the statement. Defaults to ``True``. needed_variables: list of str, optional A list of variables that are neither present in the abstract code, nor in the ``USES_VARIABLES`` statement in the template. This is only rarely necessary, an example being a `StateMonitor` where the names of the variables are neither known to the template nor included in the abstract code statements. additional_variables : dict-like, optional A mapping of names to `Variable` objects, used in addition to the variables saved in `group`. level : int, optional How far to go up in the stack to find the call frame. run_namespace : dict-like, optional An additional namespace that is used for variable lookup (if not defined, the implicit namespace of local variables is used). template_kwds : dict, optional A dictionary of additional information that is passed to the template. override_conditional_write: list of str, optional A list of variable names which are used as conditions (e.g. for refractoriness) which should be ignored. ''' if isinstance(code, str): code = {None: code} msg = 'Creating code object (group=%s, template name=%s) for abstract code:\n' % ( group.name, template_name) msg += indent(code_representation(code)) logger.debug(msg) from brian2.devices import get_device device = get_device() if override_conditional_write is None: override_conditional_write = set([]) else: override_conditional_write = set(override_conditional_write) if check_units: for c in code.values(): check_code_units(c, group, additional_variables=additional_variables, level=level + 1, run_namespace=run_namespace) codeobj_class = device.code_object_class(group.codeobj_class) template = getattr(codeobj_class.templater, template_name) all_variables = dict(group.variables) if additional_variables is not None: all_variables.update(additional_variables) # Determine the identifiers that were used used_known = set() unknown = set() for v in code.values(): _, uk, u = analyse_identifiers(v, all_variables, recursive=True) used_known |= uk unknown |= u logger.debug('Unknown identifiers in the abstract code: ' + ', '.join(unknown)) # Only pass the variables that are actually used variables = group.resolve_all(used_known | unknown, additional_variables=additional_variables, run_namespace=run_namespace, level=level + 1) conditional_write_variables = {} # Add all the "conditional write" variables for var in variables.itervalues(): cond_write_var = getattr(var, 'conditional_write', None) if cond_write_var in override_conditional_write: continue if cond_write_var is not None and cond_write_var not in variables.values( ): if cond_write_var.name in variables: raise AssertionError( ('Variable "%s" is needed for the ' 'conditional write mechanism of variable ' '"%s". Its name is already used for %r.') % (cond_write_var.name, var.name, variables[cond_write_var.name])) conditional_write_variables[cond_write_var.name] = cond_write_var variables.update(conditional_write_variables) # Add variables that are not in the abstract code, nor specified in the # template but nevertheless necessary if needed_variables is None: needed_variables = [] # Also add the variables that the template needs variables.update( group.resolve_all( set(needed_variables) | set(template.variables), additional_variables=additional_variables, run_namespace=run_namespace, level=level + 1, do_warn=False)) # no warnings for internally used variables if name is None: if group is not None: name = '%s_%s_codeobject*' % (group.name, template_name) else: name = '%s_codeobject*' % template_name all_variable_indices = copy.copy(group.variables.indices) if additional_variables is not None: all_variable_indices.update(additional_variables.indices) if variable_indices is not None: all_variable_indices.update(variable_indices) # Make "conditional write" variables use the same index as the variable # that depends on them for varname, var in variables.iteritems(): cond_write_var = getattr(var, 'conditional_write', None) if cond_write_var is not None: all_variable_indices[ cond_write_var.name] = all_variable_indices[varname] # Add the indices needed by the variables varnames = variables.keys() for varname in varnames: var_index = all_variable_indices[varname] if not var_index in ('_idx', '0'): variables[var_index] = all_variables[var_index] return device.code_object( owner=group, name=name, abstract_code=code, variables=variables, template_name=template_name, variable_indices=all_variable_indices, template_kwds=template_kwds, codeobj_class=group.codeobj_class, override_conditional_write=override_conditional_write, )
def create_runner_codeobj(group, code, template_name, user_code=None, variable_indices=None, name=None, check_units=True, needed_variables=None, additional_variables=None, level=0, run_namespace=None, template_kwds=None, override_conditional_write=None, codeobj_class=None ): ''' Create a `CodeObject` for the execution of code in the context of a `Group`. Parameters ---------- group : `Group` The group where the code is to be run code : str or dict of str The code to be executed. template_name : str The name of the template to use for the code. user_code : str, optional The code that had been specified by the user before other code was added automatically. If not specified, will be assumed to be identical to ``code``. variable_indices : dict-like, optional A mapping from `Variable` objects to index names (strings). If none is given, uses the corresponding attribute of `group`. name : str, optional A name for this code object, will use ``group + '_codeobject*'`` if none is given. check_units : bool, optional Whether to check units in the statement. Defaults to ``True``. needed_variables: list of str, optional A list of variables that are neither present in the abstract code, nor in the ``USES_VARIABLES`` statement in the template. This is only rarely necessary, an example being a `StateMonitor` where the names of the variables are neither known to the template nor included in the abstract code statements. additional_variables : dict-like, optional A mapping of names to `Variable` objects, used in addition to the variables saved in `group`. level : int, optional How far to go up in the stack to find the call frame. run_namespace : dict-like, optional An additional namespace that is used for variable lookup (if not defined, the implicit namespace of local variables is used). template_kwds : dict, optional A dictionary of additional information that is passed to the template. override_conditional_write: list of str, optional A list of variable names which are used as conditions (e.g. for refractoriness) which should be ignored. codeobj_class : class, optional The `CodeObject` class to run code with. If not specified, defaults to the `group`'s ``codeobj_class`` attribute. ''' if name is None: if group is not None: name = '%s_%s_codeobject*' % (group.name, template_name) else: name = '%s_codeobject*' % template_name if user_code is None: user_code = code if isinstance(code, str): code = {None: code} user_code = {None: user_code} msg = 'Creating code object (group=%s, template name=%s) for abstract code:\n' % (group.name, template_name) msg += indent(code_representation(code)) logger.debug(msg) from brian2.devices import get_device device = get_device() if override_conditional_write is None: override_conditional_write = set([]) else: override_conditional_write = set(override_conditional_write) if check_units: for c in code.values(): # This is the first time that the code is parsed, catch errors try: check_code_units(c, group, additional_variables=additional_variables, level=level+1, run_namespace=run_namespace) except (SyntaxError, KeyError, ValueError) as ex: error_msg = _error_msg(c, name) raise ValueError(error_msg + str(ex)) if codeobj_class is None: codeobj_class = device.code_object_class(group.codeobj_class) else: codeobj_class = device.code_object_class(codeobj_class) template = getattr(codeobj_class.templater, template_name) all_variables = dict(group.variables) if additional_variables is not None: all_variables.update(additional_variables) # Determine the identifiers that were used identifiers = set() user_identifiers = set() for v, u_v in zip(code.values(), user_code.values()): _, uk, u = analyse_identifiers(v, all_variables, recursive=True) identifiers |= uk | u _, uk, u = analyse_identifiers(u_v, all_variables, recursive=True) user_identifiers |= uk | u # Only pass the variables that are actually used variables = group.resolve_all(identifiers, user_identifiers, additional_variables=additional_variables, run_namespace=run_namespace, level=level+1) conditional_write_variables = {} # Add all the "conditional write" variables for var in variables.itervalues(): cond_write_var = getattr(var, 'conditional_write', None) if cond_write_var in override_conditional_write: continue if cond_write_var is not None and cond_write_var not in variables.values(): if cond_write_var.name in variables: raise AssertionError(('Variable "%s" is needed for the ' 'conditional write mechanism of variable ' '"%s". Its name is already used for %r.') % (cond_write_var.name, var.name, variables[cond_write_var.name])) conditional_write_variables[cond_write_var.name] = cond_write_var variables.update(conditional_write_variables) # Add variables that are not in the abstract code, nor specified in the # template but nevertheless necessary if needed_variables is None: needed_variables = [] # Also add the variables that the template needs variables.update(group.resolve_all(set(needed_variables) | set(template.variables), # template variables are not known to the user: user_identifiers=set(), additional_variables=additional_variables, run_namespace=run_namespace, level=level+1)) all_variable_indices = copy.copy(group.variables.indices) if additional_variables is not None: all_variable_indices.update(additional_variables.indices) if variable_indices is not None: all_variable_indices.update(variable_indices) # Make "conditional write" variables use the same index as the variable # that depends on them for varname, var in variables.iteritems(): cond_write_var = getattr(var, 'conditional_write', None) if cond_write_var is not None: all_variable_indices[cond_write_var.name] = all_variable_indices[varname] # Add the indices needed by the variables varnames = variables.keys() for varname in varnames: var_index = all_variable_indices[varname] if not var_index in ('_idx', '0'): variables[var_index] = all_variables[var_index] return device.code_object(owner=group, name=name, abstract_code=code, variables=variables, template_name=template_name, variable_indices=all_variable_indices, template_kwds=template_kwds, codeobj_class=codeobj_class, override_conditional_write=override_conditional_write, )