Files
mercury/library/version_bitmap.m
Simon Taylor 9c650e1d83 Improvements for bitmap.m, to make it useable as a general container
Estimated hours taken: 80
Branches: main

Improvements for bitmap.m, to make it useable as a general container
for binary data.

library/bitmap.m:
runtime/mercury_bitmap.c:
runtime/mercury_bitmap.h:
	Specialize the representation of bitmaps to an array of unsigned
	bytes defined as a foreign type.

	This is better than building on top of array(int) because it:
	- is better for interfacing with foreign code
	- has a more sensible machine-independent comparison order
	  (same as array(bool))
	- avoids storing the size twice
	- has more efficient copying, unification, comparison and tabling
	  (although we should probably specialize the handling of array(int)
	  and isomorphic types as well)
	- uses GC_MALLOC_ATOMIC to avoid problems with bit patterns that look
	  like pointers (although we should do that for array(int) as well)

	XXX The code for the Java and IL backends is untested.
	Building the library in grade Java with Sun JDK 1.6 failed (but
	at least passed error checking), and I don't have access to a
	copy of MSVS.NET.  The foreign code that needs to be tested is
	trivial.

	Add fields `bit', `bits' and `byte' to get/set a single bit,
	multiple bits (from an int) or an 8 bit byte.

	Add functions for converting bitmaps to hex strings and back,
	for use by stream.string_writer.write and deconstruct.functor/4.

	bitmap.intersect was buggy in the case where the input bitmaps
	had a different size.  Given that bitmaps are implemented with
	a fixed domain (lookups out of range throw an exception), it
	makes more sense to throw an exception in that case anyway,
	so all of the set operations do that now.

	The difference operation actually performed xor.  Fix it and
	add an xor function.

library/version_bitmap.m:
	This hasn't been fully updated to be the same as bitmap.m.
	The payoff would be much less because foreign code can't
	really do anything with version_bitmaps.

	Add a `bit' field.

	Deprecate the `get/2' function in favour of the `bit' field.

	Fix the union, difference, intersection and xor functions
	as for bitmap.m.

	Fix comparison of version_arrays so that it uses the same
	method as array.m: compare size then elements in order.
	The old code found version_arrays to be equal if one was
	a suffix of the other.

library/char.m:
	Add predicates for converting between hex digits and integers.

library/io.m:
library/stream.string_writer.m:
library/term.m:
	Read and write bitmaps.

runtime/mercury_type_info.h:
runtime/mercury_deep_copy_body.h:
runtime/mercury_mcpp.h:
runtime/mercury_table_type_body.h:
runtime/mercury_tabling_macros.h:
runtime/mercury_unify_compare_body.h:
runtime/mercury_construct.c:
runtime/mercury_deconstruct.c:
runtime/mercury_term_size.c:
runtime/mercury_string.h:
library/construct.m:
library/deconstruct.m
compiler/prog_type.m:
compiler/mlds_to_gcc.m:
compiler/rtti.m:
	Add a MR_TypeCtorRep for bitmaps, and handle it in the library
	and runtinme.

library/Mercury.options:
	Compile bitmap.m with `--no-warn-insts-without-matching-type'.

runtime/mercury_type_info.h:
	Bump MR_RTTI_VERSION.

NEWS:
	Document the changes.

tests/hard_coded/Mmakefile:
tests/hard_coded/bitmap_test.m:
tests/hard_coded/bitmap_simple.m:
tests/hard_coded/bitmap_tester.m:
tests/hard_coded/bitmap_test.exp:
tests/tabling/Mmakefile:
tests/tabling/expand_bitmap.m:
tests/tabling/expand_bitmap.exp:
tests/hard_coded/version_array_test.m:
tests/hard_coded/version_array_test.exp:
	Test cases.
2007-02-13 01:59:04 +00:00

395 lines
13 KiB
Mathematica

%-----------------------------------------------------------------------------%
% Copyright (C) 2004-2007 The University of Melbourne
% This file may only be copied under the terms of the GNU Library General
% Public License - see the file COPYING.LIB in the Mercury distribution.
% vim: ft=mercury ts=4 sw=4 et wm=0 tw=0
%-----------------------------------------------------------------------------%
%
% File: version_bitmap.m.
% Author: Ralph Becket <rafe@cs.mu.oz.au>.
% Stability: low.
%
% (See the header comments in version_types.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.
% new(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 new(int, bool) = version_bitmap.
% Returns the number of bits in a version_bitmap.
%
:- func num_bits(version_bitmap) = int.
% 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) and is_clear(BM, I) succeed iff bit I in BM
% is set or clear respectively.
%
:- pred is_set(version_bitmap::in, int::in) is semidet.
:- pred is_clear(version_bitmap::in, int::in) is semidet.
% Get the given bit.
%
:- func version_bitmap ^ bit(int) = bool.
% Set the given bit.
%
:- func (version_bitmap ^ bit(int) := bool) = version_bitmap.
% 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.
% 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
% are 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.
% 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.
:- interface.
% get(BM, I) returns `yes' if is_set(BM, I) and `no' otherwise.
% Replaced by `BM ^ bit(I)'.
:- func get(version_bitmap, int) = bool.
:- pragma obsolete(get/2).
:- implementation.
:- import_module exception.
:- 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).
%-----------------------------------------------------------------------------%
new(N, B) = BM :-
( if N < 0 then
throw(software_error("version_bitmap.new: 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 :-
( if N =< 0 then
BM = new(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)
).
resize(N, B, BM, resize(BM, N, B)).
%-----------------------------------------------------------------------------%
:- 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) = BM ^ elem(0).
%-----------------------------------------------------------------------------%
:- pred in_range(version_bitmap::in, int::in) is semidet.
in_range(BM, I) :- 0 =< I, I < num_bits(BM).
%-----------------------------------------------------------------------------%
BM ^ bit(I) = ( if is_set(BM, I) then yes else no ).
(BM ^ bit(I) := yes) = set(BM, I).
(BM ^ bit(I) := no) = clear(BM, I).
%-----------------------------------------------------------------------------%
set(BM, I) =
( if in_range(BM, I)
then BM ^ elem(int_offset(I)) :=
BM ^ elem(int_offset(I)) \/ bitmask(I)
else throw(software_error("version_bitmap.set: out of range"))
).
clear(BM, I) =
( if in_range(BM, I)
then BM ^ elem(int_offset(I)) :=
BM ^ elem(int_offset(I)) /\ \bitmask(I)
else throw(software_error("version_bitmap.clear: out of range"))
).
flip(BM, I) =
( if in_range(BM, I)
then BM ^ elem(int_offset(I)) :=
BM ^ elem(int_offset(I)) `xor` bitmask(I)
else throw(software_error("version_bitmap.flip: out of range"))
).
%-----------------------------------------------------------------------------%
set(I, BM, set(BM, I)).
clear(I, BM, clear(BM, I)).
flip(I, BM, flip(BM, I)).
%-----------------------------------------------------------------------------%
is_set(BM, I) :-
( if in_range(BM, I)
then BM ^ elem(int_offset(I)) /\ bitmask(I) \= 0
else throw(software_error("version_bitmap.is_set: out of range"))
).
is_clear(BM, I) :-
( if in_range(BM, I)
then BM ^ elem(int_offset(I)) /\ bitmask(I) = 0
else throw(software_error("version_bitmap.is_clear: out of range"))
).
%-----------------------------------------------------------------------------%
get(BM, I) = ( if is_clear(BM, I) then no else yes ).
%-----------------------------------------------------------------------------%
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
throw(software_error(
"version_bitmap.union: 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
throw(software_error(
"version_bitmap.intersect: 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
throw(software_error(
"version_bitmap.difference: 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
throw(software_error(
"version_bitmap.xor: 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 the 1 on 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
% bitmask(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)).
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%