1''' 2This module contains helper classes for alignment arithmetic and checks 3''' 4 5from sys import version_info 6 7if version_info[:2] >= (3, 5): 8 from math import gcd 9else: 10 from fractions import gcd 11 12class Alignment(object): 13 14 def __init__(self, align=4, offset=0): 15 self.align = align 16 # normalize the offset (just in case) 17 self.offset = offset % align 18 19 20 def __eq__(self, other): 21 return self.align == other.align and self.offset == other.offset 22 23 def __str__(self): 24 return "(align=%d, offset=%d)" % (self.align, self.offset) 25 26 @staticmethod 27 def for_primitive_type(size): 28 # compute the required start_alignment based on the size of the type 29 if size % 8 == 0: 30 # do 8-byte primitives require 8-byte alignment in X11? 31 return Alignment(8,0) 32 elif size % 4 == 0: 33 return Alignment(4,0) 34 elif size % 2 == 0: 35 return Alignment(2,0) 36 else: 37 return Alignment(1,0) 38 39 40 def align_after_fixed_size(self, size): 41 new_offset = (self.offset + size) % self.align 42 return Alignment(self.align, new_offset) 43 44 45 def is_guaranteed_at(self, external_align): 46 ''' 47 Assuming the given external_align, checks whether 48 self is fulfilled for all cases. 49 Returns True if yes, False otherwise. 50 ''' 51 if self.align == 1 and self.offset == 0: 52 # alignment 1 with offset 0 is always fulfilled 53 return True 54 55 if external_align is None: 56 # there is no external align -> fail 57 return False 58 59 if external_align.align < self.align: 60 # the external align guarantees less alignment -> not guaranteed 61 return False 62 63 if external_align.align % self.align != 0: 64 # the external align cannot be divided by our align 65 # -> not guaranteed 66 # (this can only happen if there are alignments that are not 67 # a power of 2, which is highly discouraged. But better be 68 # safe and check for it) 69 return False 70 71 if external_align.offset % self.align != self.offset: 72 # offsets do not match 73 return False 74 75 return True 76 77 78 def combine_with(self, other): 79 # returns the alignment that is guaranteed when 80 # both, self or other, can happen 81 new_align = gcd(self.align, other.align) 82 new_offset_candidate1 = self.offset % new_align 83 new_offset_candidate2 = other.offset % new_align 84 if new_offset_candidate1 == new_offset_candidate2: 85 new_offset = new_offset_candidate1 86 else: 87 offset_diff = abs(new_offset_candidate2 - new_offset_candidate1) 88 new_align = gcd(new_align, offset_diff) 89 new_offset_candidate1 = self.offset % new_align 90 new_offset_candidate2 = other.offset % new_align 91 assert new_offset_candidate1 == new_offset_candidate2 92 new_offset = new_offset_candidate1 93 # return the result 94 return Alignment(new_align, new_offset) 95 96 97class AlignmentLog(object): 98 99 def __init__(self): 100 self.ok_list = [] 101 self.fail_list = [] 102 self.verbosity = 1 103 104 def __str__(self): 105 result = "" 106 107 # output the OK-list 108 for (align_before, field_name, type_obj, callstack, align_after) in self.ok_list: 109 stacksize = len(callstack) 110 indent = ' ' * stacksize 111 if self.ok_callstack_is_relevant(callstack): 112 if field_name is None or field_name == "": 113 result += (" %sok: %s:\n\t%sbefore: %s, after: %s\n" 114 % (indent, str(type_obj), indent, str(align_before), str(align_after))) 115 else: 116 result += (" %sok: field \"%s\" in %s:\n\t%sbefore: %s, after: %s\n" 117 % (indent, str(field_name), str(type_obj), 118 indent, str(align_before), str(align_after))) 119 if self.verbosity >= 1: 120 result += self.callstack_to_str(indent, callstack) 121 122 # output the fail-list 123 for (align_before, field_name, type_obj, callstack, reason) in self.fail_list: 124 stacksize = len(callstack) 125 indent = ' ' * stacksize 126 if field_name is None or field_name == "": 127 result += (" %sfail: align %s is incompatible with\n\t%s%s\n\t%sReason: %s\n" 128 % (indent, str(align_before), indent, str(type_obj), indent, reason)) 129 else: 130 result += (" %sfail: align %s is incompatible with\n\t%sfield \"%s\" in %s\n\t%sReason: %s\n" 131 % (indent, str(align_before), indent, str(field_name), str(type_obj), indent, reason)) 132 133 if self.verbosity >= 1: 134 result += self.callstack_to_str(indent, callstack) 135 136 137 return result 138 139 140 def callstack_to_str(self, indent, callstack): 141 result = "\t%scallstack: [\n" % indent 142 for stack_elem in callstack: 143 result += "\t %s%s\n" % (indent, str(stack_elem)) 144 result += "\t%s]\n" % indent 145 return result 146 147 148 def ok_callstack_is_relevant(self, ok_callstack): 149 # determine whether an ok callstack is relevant for logging 150 if self.verbosity >= 2: 151 return True 152 153 # empty callstacks are always relevant 154 if len(ok_callstack) == 0: 155 return True 156 157 # check whether the ok_callstack is a subset or equal to a fail_callstack 158 for (align_before, field_name, type_obj, fail_callstack, reason) in self.fail_list: 159 if len(ok_callstack) <= len(fail_callstack): 160 zipped = zip(ok_callstack, fail_callstack[:len(ok_callstack)]) 161 is_subset = all([i == j for i, j in zipped]) 162 if is_subset: 163 return True 164 165 return False 166 167 168 def ok(self, align_before, field_name, type_obj, callstack, align_after): 169 self.ok_list.append((align_before, field_name, type_obj, callstack, align_after)) 170 171 def fail(self, align_before, field_name, type_obj, callstack, reason): 172 self.fail_list.append((align_before, field_name, type_obj, callstack, reason)) 173 174 def append(self, other): 175 self.ok_list.extend(other.ok_list) 176 self.fail_list.extend(other.fail_list) 177 178 def ok_count(self): 179 return len(self.ok_list) 180 181 182 183