import re from operator import __and__ from functools import reduce class WordBase(object): def slice_allowempty(self, range): if not isinstance(range, slice): range = slice(range, range) hi, lo = range.start, range.stop if hi < lo: return empty return self[range] def __and__(self, other): if isinstance(other, WordBase): return BitsUnion(self, other) raise NotImplementedError() class BitsUnion(WordBase): def __init__(self, *parts): self.parts = parts def __repr__(self): return "BitsUnion({})".format(', '.join(repr(part) for part in self.parts)) def as_value(self, **kwargs): """ reduce down to a single Bitfield value. >>> BitsUnion(Bitfield(0x12, 8), Bitfield(0x3, 4), Bitfield(0x4, 4)).as_value() Bitfield(0x1234, 16) >>> BitsUnion(Bitfield(0x12, 8), LabelRef("test", 0, "imm", slice(11, 4))).as_value(labels={'test': 0xf34f}) Bitfield(0x1234, 16) """ return reduce(__and__, (part.as_value(**kwargs) for part in self.parts)) @property def size(self): """ get the total size. >>> BitsUnion(Bitfield(0x12, 8), Bitfield(0x3, 4), Bitfield(0x4, 4)).size 16 >>> BitsUnion(Bitfield(0x12, 8), LabelRef("test", 0, "imm", slice(11, 4))).size 16 """ return sum(part.size for part in self.parts) def __getitem__(self, range): """ slice a union given hi and lo bit indices. >>> BitsUnion(Bitfield(0x12, 8), Bitfield(0x3, 4), Bitfield(0x4, 4))[11:4] Bitfield(0x23, 8) >>> BitsUnion(Bitfield(0x12, 8), LabelRef("test", 0, "imm", slice(11, 4)))[11:4] BitsUnion(Bitfield(0x2, 4), LabelRef('test', 0, ('imm', 32), 11:8)) """ if not isinstance(range, slice): range = slice(range, range) hi, lo = range.start, range.stop size = self.size if hi < lo: raise ValueError("cant slice reverse range") elif lo < 0 or hi >= size: raise ValueError("slice [{0.start}:{0.stop}] out of range of {1}".format(range, self)) i = size result = Bitfield(0, 0) for part in self.parts: p_size = part.size i -= p_size if i > hi: # still above hi continue r_hi = min(p_size - 1, hi - i) r_lo = max(0, lo - i) result = result & part[r_hi:r_lo] if i <= lo: # past lo break return result def __and__(self, other): if isinstance(other, BitsUnion): return BitsUnion(*self.parts, *other.parts) elif isinstance(other, WordBase): return BitsUnion(*self.parts, other) raise NotImplementedError() class Bitfield(WordBase): def __init__(self, val, size, extra=()): if val < 0: min = -(1 << (size - 1)) max = -1 - min if val > max or val < min: raise ValueError("value {} out of i{} range [{};{}]".format(val, size, min, max)) if val < 0: val = (1 << size) + val self.val = val self.size = size self.extra = extra def __repr__(self): """ format for debugging. >>> Bitfield(0, 8) Bitfield(0x0, 8) >>> Bitfield(16, 8) Bitfield(0x10, 8) >>> Bitfield(-16, 8) Bitfield(0xf0, 8) >>> Bitfield(3, 5) Bitfield(0x3, 5) >>> Bitfield(3, 4, extra=('hello',)) Bitfield(0x3, 4, extra=('hello',)) """ extra = '' if self.extra: extra = ", extra={!r}".format(self.extra) return "Bitfield(0x{:x}, {}{})".format(self.val, self.size, extra) def __str__(self): """ format as a word. >>> str(Bitfield(0, 8)) '00/8' >>> str(Bitfield(16, 8)) '10/8' >>> str(Bitfield(3, 8)) '03/8' >>> str(Bitfield(3, 5)) '03/5' >>> str(Bitfield(3, 4)) '3/4' >>> str(Bitfield(3, 4, extra=('hello',))) '3/hello/4' """ digits = 1 + ((self.size - 1) // 4) base = "{:x}".format(self.val) base = (digits - len(base)) * '0' + base return '/'.join([base, *self.extra, str(self.size)]) def as_value(self, **kwargs): return self def __getitem__(self, range): """ slice a bitfield given hi and lo bit indices. >>> Bitfield(0x7f, 8)[7:4] Bitfield(0x7, 4) >>> Bitfield(0b100, 3)[2:2] Bitfield(0x1, 1) >>> Bitfield(0x12345678, 32)[7:0] Bitfield(0x78, 8) >>> Bitfield(0x12345678, 32)[15:8] Bitfield(0x56, 8) >>> Bitfield(0x12345678, 32)[23:16] Bitfield(0x34, 8) >>> Bitfield(0xf, 4)[4:0] Traceback (most recent call last): ... ValueError: slice [4:0] out of range of f/4 >>> Bitfield(0xf, 4)[2:3] Traceback (most recent call last): ... ValueError: cant slice reverse range >>> Bitfield(0xf, 4)[3:-1] Traceback (most recent call last): ... ValueError: slice [3:-1] out of range of f/4 """ if not isinstance(range, slice): range = slice(range, range) hi, lo = range.start, range.stop if hi < lo: raise ValueError("cant slice reverse range") elif lo < 0 or hi >= self.size: raise ValueError("slice [{0.start}:{0.stop}] out of range of {1}".format(range, self)) size = hi - lo + 1 val = (self.val >> lo) & ((1 << size) - 1) return Bitfield(val, size) def __and__(self, other): """ concatenate multiple bitfields. >>> Bitfield(0b00, 2) & Bitfield(0b10, 2) Bitfield(0x2, 4) >>> Bitfield(0b110, 3) & Bitfield(0b0110, 4) & Bitfield(0b1, 1) Bitfield(0xcd, 8) """ if isinstance(other, Bitfield): return Bitfield(self.val << other.size | other.val, self.size + other.size) elif isinstance(other, WordBase): return BitsUnion(self, other) raise NotImplementedError() global_slice = slice class LabelRef(WordBase): mode_re = re.compile(r'^(imm|off)(\d+)?$') def __init__(self, label, offset, mode, slice=None): if isinstance(mode, str): match = LabelRef.mode_re.match(mode) assert match, ValueError("invalid label reference tag") mode = (match.group(1), int(match.group(2) or 32)) self.label = label self.mode = mode self.slice = slice or global_slice(self.mode[1]-1, 0) self.offset = offset def __repr__(self): """ format for debugging. >>> LabelRef("label", 0, ("imm", 32), slice(31,31)) LabelRef('label', 0, ('imm', 32), 31:31) >>> LabelRef("label", 4, ("imm", 32), slice(11,0)) LabelRef('label', 4, ('imm', 32), 11:0) >>> LabelRef("test", -4, "off32") LabelRef('test', -4, ('off', 32), 31:0) >>> LabelRef("test", 0, "off") LabelRef('test', 0, ('off', 32), 31:0) """ return "LabelRef({0!r}, {1}, {2}, {3.start}:{3.stop})".format(self.label, self.offset, self.mode, self.slice) def __str__(self): """ format as a word. >>> str(LabelRef("label", 0, ("imm", 32), slice(31,31))) 'label/imm32/[31:31]' >>> str(LabelRef("label", 0, ("imm", 32), slice(11,0))) 'label/imm32/[11:0]' >>> str(LabelRef("test", 4, "off32")) 'test+4/off32/[31:0]' >>> str(LabelRef("test", -12, "off")) 'test-12/off32/[31:0]' """ offset = '' if self.offset != 0: offset = "{:+}".format(self.offset) return "{0}{1}/{2[0]}{2[1]}/[{3.start}:{3.stop}]".format(self.label, offset, self.mode, self.slice) @property def size(self): return self.slice.start - self.slice.stop + 1 def as_value(self, labels={}, **kwargs): """ resolve the reference. >>> labels = {'test': 0xabcd1234, 'main': 0x8000} >>> LabelRef("test", 0, "imm").as_value(labels=labels) Bitfield(0xabcd1234, 32) >>> LabelRef("test", 0, "imm", slice(15, 0)).as_value(labels=labels) Bitfield(0x1234, 16) >>> LabelRef("test", 0, "imm", slice(31, 12)).as_value(labels=labels) Bitfield(0xabcd1, 20) >>> LabelRef("test", 4, "imm", slice(15, 0)).as_value(labels=labels) Bitfield(0x1238, 16) >>> LabelRef("test", 4, "imm", slice(15, 4)).as_value(labels=labels) Bitfield(0x123, 12) >>> LabelRef("main", 0, "off").as_value(labels=labels, pc=0x6000) Bitfield(0x2000, 32) >>> LabelRef("main", 0, "off").as_value(labels=labels, pc=0x800a) Bitfield(0xfffffff6, 32) >>> LabelRef("main", 0, "off", slice(7, 0)).as_value(labels=labels, pc=0x8012) Bitfield(0xee, 8) >>> LabelRef("main", 0, "off8").as_value(labels=labels, pc=0x8012) Bitfield(0xee, 8) >>> LabelRef("main", 0, "off4").as_value(labels=labels, pc=0x8012) Traceback (most recent call last): ... ValueError: value -18 out of i4 range [-8;7] >>> LabelRef("main", 4, "off", slice(7, 0)).as_value(labels=labels, pc=0x70fa) Bitfield(0xa, 8) """ if self.label not in labels: raise ValueError("label '{}' unresolved".format(self.label)) value = labels[self.label] + self.offset if self.mode[0] == 'off': assert 'pc' in kwargs, TypeError("'pc' is required to resolve offset-references") value = value - kwargs['pc'] field = Bitfield(value, self.mode[1]) return field[self.slice] def __getitem__(self, range): """ slice a label using hi and lo bit indices. >>> label = LabelRef("label", 0, "imm32", slice(31,0)) >>> label.size 32 >>> label[31] LabelRef('label', 0, ('imm', 32), 31:31) >>> label[11:0] LabelRef('label', 0, ('imm', 32), 11:0) >>> label[11:0].size 12 >>> label[12:1] LabelRef('label', 0, ('imm', 32), 12:1) >>> label = LabelRef("abc", 0, "off32", slice(31,12)) >>> label.size 20 >>> label[9:0] LabelRef('abc', 0, ('off', 32), 21:12) >>> label[9:2] LabelRef('abc', 0, ('off', 32), 21:14) >>> label[20:8] Traceback (most recent call last): ... ValueError: slice [20:8] out of range of abc/off32/[31:12] """ if not isinstance(range, slice): range = slice(range, range) hi, lo = range.start, range.stop if hi < lo: raise ValueError("cant slice reverse range") elif lo < 0 or hi >= self.size: raise ValueError("slice [{0.start}:{0.stop}] out of range of {1}".format(range, self)) new_slice = slice(self.slice.stop + hi, self.slice.stop + lo) return LabelRef(self.label, self.offset, self.mode, new_slice) empty = Bitfield(0, 0) def u(num, bits): """ parse an unsigned integer into a bitfield. >>> u(0x08, 8) Bitfield(0x8, 8) >>> u(0xff, 8) Bitfield(0xff, 8) >>> u(0xf0, 7) Traceback (most recent call last): ... ValueError: value 240 (u8) too large for u7 field >>> u(-1, 8) Traceback (most recent call last): ... ValueError: negative value not allowed: -1 """ if num < 0: raise ValueError("negative value not allowed: {}".format(num)) if num.bit_length() > bits: raise ValueError("value {} (u{}) too large for u{} field" .format(num, num.bit_length(), bits)) return Bitfield(num, bits) def i(num, bits): """ parse a signed integer into a bitfield. >>> i(8, 8) Bitfield(0x8, 8) >>> i(-4, 8) Bitfield(0xfc, 8) >>> i(0x7f, 8) Bitfield(0x7f, 8) >>> i(-128, 8) Bitfield(0x80, 8) >>> i(128, 8) Traceback (most recent call last): ... ValueError: value 128 (i9) too large for i8 field [-128;127] >>> i(-129, 8) Traceback (most recent call last): ... ValueError: value -129 (i9) too large for i8 field [-128;127] """ min = -(1 << (bits - 1)) max = -1 - min if num > max or num < min: raise ValueError("value {} (i{}) too large for i{} field [{};{}]" .format(num, num.bit_length()+1, bits, min, max)) if num < 0: num = (1 << bits) + num return u(num, bits) def from_part(part): """ parse a size-tagged subv part into a bit. >>> from_part((0x12, 2)) Bitfield(0x12, 2) >>> from_part((0x12, 2, 'extra')) Bitfield(0x12, 2) """ val = int(part[0]) size = part[1] return Bitfield(val, int(size))