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