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