1891601f5Smrg'''
2891601f5SmrgThis module contains helper classes for alignment arithmetic and checks
3891601f5Smrg'''
4891601f5Smrg
5d5c9b07bSmrgfrom sys import version_info
6d5c9b07bSmrg
7d5c9b07bSmrgif version_info[:2] >= (3, 5):
8d5c9b07bSmrg    from math import gcd
9d5c9b07bSmrgelse:
10d5c9b07bSmrg    from fractions import gcd
11891601f5Smrg
12891601f5Smrgclass Alignment(object):
13891601f5Smrg
14891601f5Smrg    def __init__(self, align=4, offset=0):
15891601f5Smrg        self.align = align
16891601f5Smrg        # normalize the offset (just in case)
17891601f5Smrg        self.offset = offset % align
18891601f5Smrg
19891601f5Smrg
20891601f5Smrg    def __eq__(self, other):
21891601f5Smrg        return self.align == other.align and self.offset == other.offset
22891601f5Smrg
23891601f5Smrg    def __str__(self):
24a27842ffSmrg        return "(align=%d, offset=%d)" % (self.align, self.offset)
25891601f5Smrg
26891601f5Smrg    @staticmethod
27891601f5Smrg    def for_primitive_type(size):
28a27842ffSmrg        # compute the required start_alignment based on the size of the type
29a27842ffSmrg        if size % 8 == 0:
30891601f5Smrg            # do 8-byte primitives require 8-byte alignment in X11?
31891601f5Smrg            return Alignment(8,0)
32891601f5Smrg        elif size % 4 == 0:
33891601f5Smrg            return Alignment(4,0)
34891601f5Smrg        elif size % 2 == 0:
35891601f5Smrg            return Alignment(2,0)
36891601f5Smrg        else:
37891601f5Smrg            return Alignment(1,0)
38891601f5Smrg
39891601f5Smrg
40891601f5Smrg    def align_after_fixed_size(self, size):
41a27842ffSmrg        new_offset = (self.offset + size) % self.align
42891601f5Smrg        return Alignment(self.align, new_offset)
43891601f5Smrg
44891601f5Smrg
45891601f5Smrg    def is_guaranteed_at(self, external_align):
46891601f5Smrg        '''
47891601f5Smrg        Assuming the given external_align, checks whether
48891601f5Smrg        self is fulfilled for all cases.
49a27842ffSmrg        Returns True if yes, False otherwise.
50891601f5Smrg        '''
51891601f5Smrg        if self.align == 1 and self.offset == 0:
52891601f5Smrg            # alignment 1 with offset 0 is always fulfilled
53891601f5Smrg            return True
54891601f5Smrg
55891601f5Smrg        if external_align is None:
56891601f5Smrg            # there is no external align -> fail
57891601f5Smrg            return False
58891601f5Smrg
59891601f5Smrg        if external_align.align < self.align:
60891601f5Smrg            # the external align guarantees less alignment -> not guaranteed
61891601f5Smrg            return False
62891601f5Smrg
63a27842ffSmrg        if external_align.align % self.align != 0:
64891601f5Smrg            # the external align cannot be divided by our align
65a27842ffSmrg            # -> not guaranteed
66891601f5Smrg            # (this can only happen if there are alignments that are not
67891601f5Smrg            # a power of 2, which is highly discouraged. But better be
68891601f5Smrg            # safe and check for it)
69891601f5Smrg            return False
70891601f5Smrg
71891601f5Smrg        if external_align.offset % self.align != self.offset:
72891601f5Smrg            # offsets do not match
73891601f5Smrg            return False
74891601f5Smrg
75891601f5Smrg        return True
76891601f5Smrg
77891601f5Smrg
78891601f5Smrg    def combine_with(self, other):
79891601f5Smrg        # returns the alignment that is guaranteed when
80a27842ffSmrg        # both, self or other, can happen
81891601f5Smrg        new_align = gcd(self.align, other.align)
82891601f5Smrg        new_offset_candidate1 = self.offset % new_align
83891601f5Smrg        new_offset_candidate2 = other.offset % new_align
84891601f5Smrg        if new_offset_candidate1 == new_offset_candidate2:
85891601f5Smrg            new_offset = new_offset_candidate1
86891601f5Smrg        else:
87891601f5Smrg            offset_diff = abs(new_offset_candidate2 - new_offset_candidate1)
88891601f5Smrg            new_align = gcd(new_align, offset_diff)
89891601f5Smrg            new_offset_candidate1 = self.offset % new_align
90891601f5Smrg            new_offset_candidate2 = other.offset % new_align
91a27842ffSmrg            assert new_offset_candidate1 == new_offset_candidate2
92a27842ffSmrg            new_offset = new_offset_candidate1
93891601f5Smrg        # return the result
94891601f5Smrg        return Alignment(new_align, new_offset)
95891601f5Smrg
96891601f5Smrg
97891601f5Smrgclass AlignmentLog(object):
98891601f5Smrg
99891601f5Smrg    def __init__(self):
100a27842ffSmrg        self.ok_list = []
101a27842ffSmrg        self.fail_list = []
102a27842ffSmrg        self.verbosity = 1
103891601f5Smrg
104891601f5Smrg    def __str__(self):
105a27842ffSmrg        result = ""
106891601f5Smrg
107a27842ffSmrg        # output the OK-list
108a27842ffSmrg        for (align_before, field_name, type_obj, callstack, align_after) in self.ok_list:
109a27842ffSmrg            stacksize = len(callstack)
110891601f5Smrg            indent = '  ' * stacksize
111a27842ffSmrg            if self.ok_callstack_is_relevant(callstack):
112891601f5Smrg                if field_name is None or field_name == "":
113a27842ffSmrg                    result += ("    %sok: %s:\n\t%sbefore: %s, after: %s\n"
114a27842ffSmrg                        % (indent, str(type_obj), indent, str(align_before), str(align_after)))
115a27842ffSmrg                else:
116a27842ffSmrg                    result += ("    %sok: field \"%s\" in %s:\n\t%sbefore: %s, after: %s\n"
117a27842ffSmrg                        % (indent, str(field_name), str(type_obj),
118a27842ffSmrg                           indent, str(align_before), str(align_after)))
119891601f5Smrg                if self.verbosity >= 1:
120a27842ffSmrg                    result += self.callstack_to_str(indent, callstack)
121891601f5Smrg
122a27842ffSmrg        # output the fail-list
123a27842ffSmrg        for (align_before, field_name, type_obj, callstack, reason) in self.fail_list:
124a27842ffSmrg            stacksize = len(callstack)
125891601f5Smrg            indent = '  ' * stacksize
126a27842ffSmrg            if field_name is None or field_name == "":
127a27842ffSmrg                result += ("    %sfail: align %s is incompatible with\n\t%s%s\n\t%sReason: %s\n"
128a27842ffSmrg                    % (indent, str(align_before), indent, str(type_obj), indent, reason))
129a27842ffSmrg            else:
130a27842ffSmrg                result += ("    %sfail: align %s is incompatible with\n\t%sfield \"%s\" in %s\n\t%sReason: %s\n"
131a27842ffSmrg                    % (indent, str(align_before), indent, str(field_name), str(type_obj), indent, reason))
132891601f5Smrg
133891601f5Smrg            if self.verbosity >= 1:
134a27842ffSmrg                result += self.callstack_to_str(indent, callstack)
135891601f5Smrg
136891601f5Smrg
137a27842ffSmrg        return result
138891601f5Smrg
139891601f5Smrg
140891601f5Smrg    def callstack_to_str(self, indent, callstack):
141891601f5Smrg        result = "\t%scallstack: [\n" % indent
142891601f5Smrg        for stack_elem in callstack:
143891601f5Smrg            result += "\t  %s%s\n" % (indent, str(stack_elem))
144891601f5Smrg        result += "\t%s]\n" % indent
145a27842ffSmrg        return result
146891601f5Smrg
147891601f5Smrg
148891601f5Smrg    def ok_callstack_is_relevant(self, ok_callstack):
149891601f5Smrg        # determine whether an ok callstack is relevant for logging
150a27842ffSmrg        if self.verbosity >= 2:
151a27842ffSmrg            return True
152891601f5Smrg
153891601f5Smrg        # empty callstacks are always relevant
154a27842ffSmrg        if len(ok_callstack) == 0:
155891601f5Smrg            return True
156891601f5Smrg
157a27842ffSmrg        # check whether the ok_callstack is a subset or equal to a fail_callstack
158891601f5Smrg        for (align_before, field_name, type_obj, fail_callstack, reason) in self.fail_list:
159891601f5Smrg            if len(ok_callstack) <= len(fail_callstack):
160891601f5Smrg                zipped = zip(ok_callstack, fail_callstack[:len(ok_callstack)])
161a27842ffSmrg                is_subset = all([i == j for i, j in zipped])
162a27842ffSmrg                if is_subset:
163891601f5Smrg                    return True
164891601f5Smrg
165891601f5Smrg        return False
166891601f5Smrg
167891601f5Smrg
168891601f5Smrg    def ok(self, align_before, field_name, type_obj, callstack, align_after):
169a27842ffSmrg        self.ok_list.append((align_before, field_name, type_obj, callstack, align_after))
170891601f5Smrg
171891601f5Smrg    def fail(self, align_before, field_name, type_obj, callstack, reason):
172a27842ffSmrg        self.fail_list.append((align_before, field_name, type_obj, callstack, reason))
173891601f5Smrg
174891601f5Smrg    def append(self, other):
175a27842ffSmrg        self.ok_list.extend(other.ok_list)
176a27842ffSmrg        self.fail_list.extend(other.fail_list)
177891601f5Smrg
178891601f5Smrg    def ok_count(self):
179a27842ffSmrg        return len(self.ok_list)
180891601f5Smrg
181891601f5Smrg
182891601f5Smrg
183