%c---------------------------------------------------------------------------% % vim: ft=mercury ts=4 sw=4 et %---------------------------------------------------------------------------% % Copyright (C) 2004-2007, 2010-2011 The University of Melbourne % Copyright (C) 2013-2015, 2017-2019, 2022, 2024-2026 The Mercury team. % This file is distributed under the terms specified in COPYING.LIB. %---------------------------------------------------------------------------% % % File: version_bitmap.m. % Author: Ralph Becket . % Stability: medium. % % (See the header comments in version_array.m for an explanation of version % types.) % % Version bitmaps: an implementation of bitmaps using version arrays. % % The advantage of version bitmaps is that in the common, singly threaded % case, they are almost as fast as unique bitmaps, but can be treated as % ordinary ground values rather than unique values. % %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% :- module version_bitmap. :- interface. :- import_module bool. %---------------------------------------------------------------------------% :- type version_bitmap. % init(N, B) creates a version_bitmap of size N (indexed 0 .. N-1) % setting each bit if B = yes and clearing each bit if B = no. % An exception is thrown if N is negative. % :- func init(int, bool) = version_bitmap. % resize(BM, N, B) resizes version_bitmap BM to have N bits; % if N is smaller than the current number of bits in BM, then % the excess is discarded. If N is larger than the current number % of bits in BM, then the new bits are set if B = yes and cleared if % B = no. % :- func resize(version_bitmap, int, bool) = version_bitmap. % Version of the above suitable for use with state variables. % :- pred resize(int::in, bool::in, version_bitmap::in, version_bitmap::out) is det. % Returns the number of bits in a version_bitmap. % :- func num_bits(version_bitmap) = int. % get_bit(BM, I, Bit): % BM ^ bit(I) = Bit: % % Get the bit at the given index. % :- pred get_bit(version_bitmap::in, int::in, bool::out) is det. :- func bit(int, version_bitmap) = bool. % set_bit(I, NewBit, !BM): % BM0 ^ bit(I) := NewBit = BM: % % Set the bit at the given index. % :- pred set_bit(int::in, bool::in, version_bitmap::in, version_bitmap::out) is det. :- func 'bit :='(int, version_bitmap, bool) = version_bitmap. % set(BM, I), clear(BM, I) and flip(BM, I) set, clear and flip % bit I in BM respectively. An exception is thrown if I is out % of range. Predicate versions are also provided. % :- func set(version_bitmap, int) = version_bitmap. :- pred set(int::in, version_bitmap::in, version_bitmap::out) is det. :- func clear(version_bitmap, int) = version_bitmap. :- pred clear(int::in, version_bitmap::in, version_bitmap::out) is det. :- func flip(version_bitmap, int) = version_bitmap. :- pred flip(int::in, version_bitmap::in, version_bitmap::out) is det. % is_set(BM, I) succeeds if-and-only-if bit I in BM is set. % :- pred is_set(version_bitmap::in, int::in) is semidet. % is_clear(BM, I) succeeds if-and-only-if bit I in BM is clear. % :- pred is_clear(version_bitmap::in, int::in) is semidet. % Create a new copy of a version_bitmap. % :- func copy(version_bitmap) = version_bitmap. % Set operations; the second argument is altered in all cases. % :- func complement(version_bitmap) = version_bitmap. :- func union(version_bitmap, version_bitmap) = version_bitmap. :- func intersect(version_bitmap, version_bitmap) = version_bitmap. :- func difference(version_bitmap, version_bitmap) = version_bitmap. :- func xor(version_bitmap, version_bitmap) = version_bitmap. % unsafe_rewind(B) produces a version of B for which all accesses are % O(1). Invoking this predicate renders B and all later versions undefined % that were derived by performing individual updates. Only use this when % you are absolutely certain there are no live references to B or later % versions of B. % :- func unsafe_rewind(version_bitmap) = version_bitmap. % A version of the above suitable for use with state variables. % :- pred unsafe_rewind(version_bitmap::in, version_bitmap::out) is det. %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% :- implementation. :- import_module int. :- import_module require. :- import_module version_array. % A version_bitmap is represented as an array of ints where each int stores % int.bits_per_int bits. The first element of the array (index 0) % is used to hold the number of bits in the version_bitmap. This avoids % having to create a new version_bitmap cell on each update. % % NOTE: the `filler' bits in the last element of the array *must* be clear % (i.e. zero). This makes the set operations simpler to implement. % :- type version_bitmap == version_array(int). %---------------------------------------------------------------------------% init(N, B) = BM :- ( if N < 0 then error($pred, "negative size") else X = initializer(B), BM0 = (version_array.init(num_ints_required(N), X) ^ elem(0) := N), BM = clear_filler_bits(BM0) ). %---------------------------------------------------------------------------% resize(BM0, N, B) = BM :- resize(N, B, BM0, BM). resize(N, B, BM0, BM) :- ( if N =< 0 then BM = init(N, B) else X = initializer(B), NumInts = num_ints_required(N), BM1 = version_array.resize(BM0, NumInts, X), % Now we need to ensure that bits N, N+1, N+2, ... up to % the word boundary are initialized properly. int.min(num_bits(BM0), N, M), Offset = int_offset(M - 1), Mask = bitsmask(M - 1), % For bits we need to preserve. Bits = \(Mask) /\ X, % Bits we need to fill in. BM2 = (( BM1 ^ elem(0) := N ) ^ elem(Offset) := (BM1 ^ elem(Offset) /\ Mask) \/ Bits), BM = clear_filler_bits(BM2) ). %---------------------------------------------------------------------------% :- func clear_filler_bits(version_bitmap) = version_bitmap. clear_filler_bits(BM0) = BM :- N = num_bits(BM0), ( if N > 0 then Last = int_offset(N - 1), % Offset of last bit. Ksam = bitsmask(N - 1), % Masks off the filler bits. BM = BM0 ^ elem(Last) := BM0 ^ elem(Last) /\ Ksam else BM = BM0 ). %---------------------------------------------------------------------------% :- func initializer(bool) = int. initializer(no) = 0. initializer(yes) = \(0). %---------------------------------------------------------------------------% num_bits(BM) = NumBits :- version_array.lookup(BM, 0, NumBits). %---------------------------------------------------------------------------% :- pred in_range(version_bitmap::in, int::in) is semidet. in_range(BM, I) :- 0 =< I, I < num_bits(BM). %---------------------------------------------------------------------------% get_bit(BM, I, Bit) :- Bit = ( if is_set(BM, I) then yes else no ). bit(I, BM) = Bit :- get_bit(BM, I, Bit). set_bit(I, yes, BM0, BM) :- set(I, BM0, BM). set_bit(I, no, BM0, BM) :- clear(I, BM0, BM). 'bit :='(I, BM0, NewBit) = BM :- set_bit(I, NewBit, BM0, BM). %---------------------------------------------------------------------------% set(BM0, I) = BM :- set(I, BM0, BM). set(I, BM0, BM) :- ( if in_range(BM0, I) then BM = BM0 ^ elem(int_offset(I)) := BM0 ^ elem(int_offset(I)) \/ bitmask(I) else error($pred, "out of range") ). clear(BM0, I) = BM :- clear(I, BM0, BM). clear(I, BM0, BM) :- ( if in_range(BM0, I) then BM = BM0 ^ elem(int_offset(I)) := BM0 ^ elem(int_offset(I)) /\ \bitmask(I) else error($pred, "out of range") ). flip(BM0, I) = BM :- flip(I, BM0, BM). flip(I, BM0, BM) :- ( if in_range(BM0, I) then BM = BM0 ^ elem(int_offset(I)) := BM0 ^ elem(int_offset(I)) `xor` bitmask(I) else error($pred, "out of range") ). %---------------------------------------------------------------------------% is_set(BM, I) :- ( if in_range(BM, I) then BM ^ elem(int_offset(I)) /\ bitmask(I) \= 0 else error($pred, "out of range") ). is_clear(BM, I) :- ( if in_range(BM, I) then BM ^ elem(int_offset(I)) /\ bitmask(I) = 0 else error($pred, "out of range") ). %---------------------------------------------------------------------------% copy(BM) = version_array.copy(BM). %---------------------------------------------------------------------------% complement(BM) = clear_filler_bits(complement_2(BM ^ elem(0) - 1, BM)). :- func complement_2(int, version_bitmap) = version_bitmap. complement_2(WordI, BM) = ( if WordI =< 0 then BM else complement_2(WordI - 1, BM ^ elem(WordI) := \(BM ^ elem(WordI))) ). %---------------------------------------------------------------------------% union(BMa, BMb) = ( if num_bits(BMa) = num_bits(BMb) then zip(int_offset(num_bits(BMb) - 1), (\/), BMa, BMb) else func_error($pred, "version_bitmaps not the same size") ). %---------------------------------------------------------------------------% intersect(BMa, BMb) = ( if num_bits(BMa) = num_bits(BMb) then zip(int_offset(num_bits(BMb) - 1), (/\), BMa, BMb) else func_error($pred, "version_bitmaps not the same size") ). %---------------------------------------------------------------------------% difference(BMa, BMb) = ( if num_bits(BMa) = num_bits(BMb) then zip(int_offset(num_bits(BMb) - 1), (func(X, Y) = X /\ \Y), BMa, BMb) else func_error($pred, "version_bitmaps not the same size") ). %---------------------------------------------------------------------------% xor(BMa, BMb) = ( if num_bits(BMa) = num_bits(BMb) then zip(int_offset(num_bits(BMb) - 1), (func(X, Y) = X `xor` Y), BMa, BMb) else func_error($pred, "version_bitmaps not the same size") ). %---------------------------------------------------------------------------% % Applies a function to every corresponding element between +ve I % and 1 inclusive, destructively updating the second version_bitmap. % :- func zip(int, func(int, int) = int, version_bitmap, version_bitmap) = version_bitmap. zip(I, Fn, BMa, BMb) = ( if I > 0 then zip(I - 1, Fn, BMa, BMb ^ elem(I) := Fn(BMb ^ elem(I), BMa ^ elem(I))) else BMb ). %---------------------------------------------------------------------------% % The size of the version_array required to hold an N-bit version_bitmap. % :- func num_ints_required(int) = int. % We add 1 because version_arrays of size N are indexed 0 .. N - 1. % num_ints_required(N) = 1 + ( if N > 0 then int_offset(N) else 0 ). %---------------------------------------------------------------------------% % The version_array index containing the given bit. % :- func int_offset(int) = int. % We add the extra 1 on because elem(0) is used to store the % number of bits in the version_bitmap; the data are stored % in the following elements. % int_offset(I) = 1 + int.quot_bits_per_int(I). %---------------------------------------------------------------------------% % Construct the bitmask for a given bit in a word. % % E.g. assuming int.bits_per_int = 8 and I = 11 then % bitmask(I) = 2'00001000 % :- func bitmask(int) = int. % NOTE: it would be nicer to use /\ with a bitmask here rather than rem. % Do modern back-ends do the decent thing here if int.bits_per_int % is the expected power of two? % bitmask(I) = 1 `unchecked_left_shift` int.rem_bits_per_int(I). %---------------------------------------------------------------------------% % Construct the bitmask for all the bits up to and including % the given bit in a word. % % E.g. assuming int.bits_per_int = 8 and I = 11 then % bitmasks(I) = 2'00001111 % :- func bitsmask(int) = int. bitsmask(I) = BitsMask :- BitMask = bitmask(I), BitsMask = BitMask \/ (BitMask - 1). %---------------------------------------------------------------------------% unsafe_rewind(BM) = version_array.unsafe_rewind(BM). unsafe_rewind(BM, version_bitmap.unsafe_rewind(BM)). %---------------------------------------------------------------------------% :- end_module version_bitmap. %---------------------------------------------------------------------------%