diff --git a/doc/ref/integers.xml b/doc/ref/integers.xml
index 164ac37bedb..56d68968159 100644
--- a/doc/ref/integers.xml
+++ b/doc/ref/integers.xml
@@ -174,5 +174,20 @@ random integers and random choices from lists.
<#Include Label="RandomSource">
+
+
+ Bitfields
+Bitfields are a low-level feature intended to support efficient
+subdivision of immediate integers into bitfields of various widths.
+This is typically useful in implementing space-efficient and/or
+cache-efficient data structures. This feature should be used with care
+because (inter alia) it has different limitations on 32-bit and 64-bit
+architectures.
+
+<#Include Label="MakeBitfields">
+<#Include Label="BuildBitfields">
+
+
+
diff --git a/doc/ref/makedocreldata.g b/doc/ref/makedocreldata.g
index 5c549a44dd7..846e6f0bb76 100644
--- a/doc/ref/makedocreldata.g
+++ b/doc/ref/makedocreldata.g
@@ -28,6 +28,7 @@ GAPInfo.ManualDataRef:= rec(
"../../lib/attr.gd",
"../../lib/basis.gd",
"../../lib/basismut.gd",
+ "../../lib/bitfields.gd",
"../../lib/boolean.g",
"../../lib/clas.gd",
"../../lib/cmdledit.g",
diff --git a/lib/bitfields.gd b/lib/bitfields.gd
new file mode 100644
index 00000000000..586ff9912f3
--- /dev/null
+++ b/lib/bitfields.gd
@@ -0,0 +1,119 @@
+#############################################################################
+##
+#W bitfields.gd GAP library Steve Linton
+##
+##
+#Y Copyright (C) 2017 The GAP Group
+##
+## This file declares the operations for bitfields
+##
+
+
+#############################################################################
+##
+#F MakeBitfields([, [, ...]]])
+## <#GAPDoc Label="MakeBitfields">
+##
+##
+##
+##
+##
+## This function sets up the machinery for a set of bitfields of the given
+## widths. All bitfield values are treated as unsigned.
+## The total of the widths must not exceed 60 bits on 64-bit architecture
+## or 28 bits on a 32-bit architecture. For performance
+## reasons some checks that one might wish to do are ommitted. In particular,
+## the builder and setter functions do not check if the value[s] passed to them are
+## negative or too large (unless &GAP; is specially compiled for debugging).
+## Behaviour when such arguments are passed is undefined.
+##
+## You can tell which type of architecture you are running on by acccessing
+## GAPInfo.BytesPerVariable which is 8 on 64-bits and 4 on 32.
+##
+##
+## The return value when n widths are given is a record whose fields are
+##
+## widths - a copy of the arguments, for convenience,
+## getters - a list of n functions of one
+## argument each of which extracts one of the fields from an immediate integer
+## setters - a list of n functions each taking
+## two arguments: a packed value and a new value for one of its fields and
+## returning a new packed value. The
+## ith function returned the new packed value in which the ith
+## field has been replaced by the new value.
+## Note that this does NOT modify the original packed value.
+##
+##
+##
+## Two additional fields may be present if any of the field widths is
+## one. Each is a list and only has entried bound in the positions
+## corresponding to the width 1 fields.
+## booleanGetters - if the ith position of
+## this list is set, it contains a function which extracts the ith
+## field (which will have width one) and returns true if it contains 1
+## and false if it contains 0
+## booleanSetters - if the ith position of
+## this list is set, it contains a function of two arguments. The first
+## argument is a packed value, the second is true or false. It returns a
+## new packed value in which the ith field is set to 1 if the second
+## argument was true and 0 if it was false. Behaviour for any
+## other value is undefined.
+##
+##
+## <#/GAPDoc>
+
+DeclareGlobalFunction( "MakeBitfields" );
+
+##
+#############################################################################
+##
+#F BuildBitfields( [, , [...]])
+## <#GAPDoc Label="BuildBitfields">
+##
+##
+##
+##
+## This function takes one or more argument. It's first argument is a list
+## of field widths, as found in the widths entry of a record returned
+## by MakeBitfields. The remaining arguments are unsigned integer
+## values, equal in number to the entries of the list of field widths. It returns
+## a small integer in which those entries are packed into bitfields of the
+## given widths. The first entry occupies the least significant bits.
+##
+##
+
+DeclareGlobalFunction("BuildBitfields");
+
+##
+## bf := MakeBitfields(1,2,3);
+## rec( booleanGetters := [ function( data ) ... end ],
+## booleanSetters := [ function( data, val ) ... end ],
+## getters := [ function( data ) ... end, function( data ) ... end,
+## function( data ) ... end ],
+## setters := [ function( data, val ) ... end, function( data, val ) ... end,
+## function( data, val ) ... end ], widths := [ 1, 2, 3 ] )
+## gap> x := BuildBitfields(bf.widths,0,3,5);
+## 46
+## gap> bf.getters[3](x);
+## 5
+## gap> y := bf.setters[1](x,1);
+## 47
+## gap> x;
+## 46
+## gap> bf.booleanGetters[1](x);
+## false
+## gap> bf.booleanGetters[1](y);
+## true
+## ]]>
+##
+##
+## <#/GAPDoc>
+##
+
+
+
+#############################################################################
+##
+#E
+
diff --git a/lib/bitfields.gi b/lib/bitfields.gi
new file mode 100644
index 00000000000..3282eab0d38
--- /dev/null
+++ b/lib/bitfields.gi
@@ -0,0 +1,2 @@
+InstallGlobalFunction(MakeBitfields, MAKE_BITFIELDS);
+InstallGlobalFunction(BuildBitfields, BUILD_BITFIELDS);
diff --git a/lib/read3.g b/lib/read3.g
index 43c71897875..00b88ee8def 100644
--- a/lib/read3.g
+++ b/lib/read3.g
@@ -12,6 +12,7 @@ ReadLib( "extrset.gd" );
ReadLib( "extuset.gd" );
ReadLib( "dict.gd" );
+ReadLib( "bitfields.gd" );
ReadLib( "mapping.gd" );
ReadLib( "mapphomo.gd" );
diff --git a/lib/read5.g b/lib/read5.g
index 57c9c8c11b9..c04f272c913 100644
--- a/lib/read5.g
+++ b/lib/read5.g
@@ -149,6 +149,7 @@ ReadLib( "grppcatr.gi" );
ReadLib( "grppcnrm.gi" );
# files dealing with trees and hash tables
+ReadLib( "bitfields.gi" );
ReadLib( "dict.gi" );
ReadLib( "dicthf.gi" );
diff --git a/src/intfuncs.c b/src/intfuncs.c
index 684bb4e7e46..34be023f2a1 100644
--- a/src/intfuncs.c
+++ b/src/intfuncs.c
@@ -615,6 +615,211 @@ Obj FuncINT_STRING ( Obj self, Obj string )
return IntStringInternal(string);
}
+/****************************************************************************
+**
+*F SmallInt Bitfield operations
+*
+* The goal here it to set up a division of the usable bits in a small
+* integer into fields which can be accessed very quickly from GAP
+* level and quickly and conveniently from C. The purpose is to allow
+* implementation of data structures that divide up the bits within a
+* word without having to make them entirely opaque to the GAP level or
+* ridiculously slow
+*
+* The API is defined in lib/bitfields.gd and works by providing the
+* user with a collection of functions to get and set fields and
+* assemble an entire word.
+*
+* These functions are constructed here and have special handlers. The
+* information the handlers need about the size and position of the
+* bitfields are stored in some of the fields of the function header
+* which are not normally used for kernel functions. Specifically, we
+* use the NLOC_FUNC and FEXS_FUNC fields, which we alias as
+* MASK_BITFIELD_FUNC and OFFSET_BITFIELD_FUNC.
+*
+* For fields of size 1 we also offer Boolean setters and getters which
+* accept and return True for 1 and False for 0. This makes for much
+* nicer code on the GAP side.
+*
+*/
+
+
+static inline UInt MASK_BITFIELD_FUNC(Obj func)
+{
+ return NLOC_FUNC(func);
+}
+
+static inline void SET_MASK_BITFIELD_FUNC(Obj func, UInt mask)
+{
+ SET_NLOC_FUNC(func, mask);
+}
+
+static inline UInt OFFSET_BITFIELD_FUNC(Obj func)
+{
+ GAP_ASSERT(IS_INTOBJ(FEXS_FUNC(func)));
+ return INT_INTOBJ(FEXS_FUNC(func));
+}
+
+static inline void SET_OFFFSET_BITFIELD_FUNC(Obj func, UInt offset)
+{
+ return SET_FEXS_FUNC(func, INTOBJ_INT(offset));
+
+}
+
+static Obj DoFieldGetter(Obj self, Obj data)
+{
+ UInt mask = MASK_BITFIELD_FUNC(self);
+ UInt offset = OFFSET_BITFIELD_FUNC(self);
+ if (!IS_INTOBJ(data))
+ ErrorMayQuit("Field getter: argument must be small integer", 0, 0);
+ UInt x = INT_INTOBJ(data);
+ return INTOBJ_INT((x & mask)>>offset);
+}
+
+static Obj DoFieldSetter(Obj self, Obj data, Obj val)
+{
+ UInt mask = MASK_BITFIELD_FUNC(self);
+ UInt offset = OFFSET_BITFIELD_FUNC(self);
+ if (!ARE_INTOBJS(data, val))
+ ErrorMayQuit("Field Setter: both arguments must be small integers", 0,
+ 0);
+ UInt x = INT_INTOBJ(data);
+ UInt y = INT_INTOBJ(val);
+ return INTOBJ_INT((x & ~mask) | (y << offset));
+}
+
+static Obj DoBooleanFieldGetter(Obj self, Obj data)
+{
+ UInt mask = MASK_BITFIELD_FUNC(self);
+ if (!IS_INTOBJ(data))
+ ErrorMayQuit("Boolean Field getter: argument must be small integer", 0, 0);
+ UInt x = INT_INTOBJ(data);
+ return (x & mask) ? True : False;
+}
+
+static Obj DoBooleanFieldSetter(Obj self, Obj data, Obj val)
+{
+ UInt mask = MASK_BITFIELD_FUNC(self);
+ if (!IS_INTOBJ(data))
+ ErrorMayQuit("Boolean Field Setter: data must be small integer", 0,
+ 0);
+ UInt x = INT_INTOBJ(data);
+ if (val == True)
+ x |= mask;
+ else if (val == False)
+ x &= ~mask;
+ else
+ ErrorMayQuit("Boolean Field Setter: value must be true or false", 0, 0);
+ return INTOBJ_INT(x);
+}
+
+
+static Obj FuncBUILD_BITFIELDS(Obj self, Obj args)
+{
+ GAP_ASSERT(IS_PLIST(args));
+ GAP_ASSERT(LEN_PLIST(args) >= 1 && ELM_PLIST(args,1));
+ Obj widths = ELM_PLIST(args,1);
+ if (!IS_LIST(widths))
+ ErrorMayQuit("Fields builder: first argument must be list of widths", 0,
+ 0);
+ UInt nfields = LEN_LIST(widths);
+ if (LEN_PLIST(args) != nfields + 1)
+ ErrorMayQuit("Fields builder: number of values must match number of widths", 0, 0);
+ UInt x = 0;
+ UInt i;
+ for (i = nfields; i > 0; i--) {
+ GAP_ASSERT(ISB_LIST(widths, i));
+ Obj y = ELM_LIST(widths,i);
+ GAP_ASSERT(IS_INTOBJ(y));
+ x <<= INT_INTOBJ(y);
+ GAP_ASSERT(ELM_PLIST(args, i+1));
+ Obj z = ELM_PLIST(args, i+1);
+ if (!IS_INTOBJ(z))
+ ErrorMayQuit("Fields builder: values must be small integers", 0, 0);
+ GAP_ASSERT(INT_INTOBJ(z) < (1 << INT_INTOBJ(y)));
+ x |= INT_INTOBJ(z);
+ }
+ return INTOBJ_INT(x);
+}
+
+
+Obj FuncMAKE_BITFIELDS(Obj self, Obj widths)
+{
+ if (!IS_LIST(widths))
+ ErrorMayQuit("MAKE_BITFIELDS: widths must be a list", 0, 0);
+ UInt nfields = LEN_LIST(widths);
+ UInt starts[nfields + 1];
+ starts[0] = 0;
+ for (UInt i = 1; i <= nfields; i++) {
+ Obj o = ELM_LIST(widths, i);
+ if (!IS_INTOBJ(o))
+ ErrorMayQuit("MAKE_BITFIELDS: widths must be small integers", 0,
+ 0);
+ UInt width = INT_INTOBJ(o);
+ starts[i] = starts[i - 1] + width;
+ }
+ if (starts[nfields] > 8 * sizeof(UInt))
+ ErrorMayQuit("MAKE_BITFIELDS: total widths too large", 0, 0);
+
+ Obj setters = NEW_PLIST(T_PLIST_DENSE + IMMUTABLE, nfields);
+ Obj getters = NEW_PLIST(T_PLIST_DENSE + IMMUTABLE, nfields);
+ Obj bsetters = NEW_PLIST(T_PLIST + IMMUTABLE, nfields);
+ UInt bslen = 0;
+ Obj bgetters = NEW_PLIST(T_PLIST + IMMUTABLE, nfields);
+ for (UInt i = 1; i <= nfields; i++) {
+ UInt mask = (1L << starts[i]) - (1L << starts[i - 1]);
+ Obj s = NewFunctionCT(T_FUNCTION, SIZE_FUNC, "", 2,
+ "data, val", DoFieldSetter);
+ SET_MASK_BITFIELD_FUNC(s, mask);
+ SET_OFFFSET_BITFIELD_FUNC(s, starts[i - 1]);
+ SET_ELM_PLIST(setters, i, s);
+ CHANGED_BAG(setters);
+ Obj g = NewFunctionCT(T_FUNCTION, SIZE_FUNC, "", 1,
+ "data", DoFieldGetter);
+ SET_MASK_BITFIELD_FUNC(g, mask);
+ SET_OFFFSET_BITFIELD_FUNC(g, starts[i - 1]);
+ SET_ELM_PLIST(getters, i, g);
+ CHANGED_BAG(getters);
+ if (starts[i] - starts[i - 1] == 1) {
+ s = NewFunctionCT(T_FUNCTION, SIZE_FUNC, "",
+ 2, "data, val", DoBooleanFieldSetter);
+ SET_MASK_BITFIELD_FUNC(s, mask);
+ SET_OFFFSET_BITFIELD_FUNC(s, starts[i - 1]);
+ SET_ELM_PLIST(bsetters, i, s);
+ CHANGED_BAG(bsetters);
+ bslen = i;
+ g = NewFunctionCT(T_FUNCTION, SIZE_FUNC, "",
+ 1, "data", DoBooleanFieldGetter);
+ SET_MASK_BITFIELD_FUNC(g, mask);
+ SET_OFFFSET_BITFIELD_FUNC(g, starts[i - 1]);
+ SET_ELM_PLIST(bgetters, i, g);
+ CHANGED_BAG(bgetters);
+ }
+ }
+
+ SET_LEN_PLIST(setters, nfields);
+ SET_LEN_PLIST(getters, nfields);
+ SET_LEN_PLIST(bsetters, bslen);
+ SET_LEN_PLIST(bgetters, bslen);
+ if (bslen == 0) {
+ RetypeBag(bsetters, T_PLIST_EMPTY + IMMUTABLE);
+ RetypeBag(bgetters, T_PLIST_EMPTY + IMMUTABLE);
+ }
+
+ Obj ms = NEW_PREC(5);
+ AssPRec(ms, RNamName("widths"), CopyObj(widths, 0));
+ AssPRec(ms, RNamName("getters"), getters);
+ AssPRec(ms, RNamName("setters"), setters);
+ if (bslen > 0) {
+ AssPRec(ms, RNamName("booleanGetters"), bgetters);
+ AssPRec(ms, RNamName("booleanSetters"), bsetters);
+ }
+ SortPRecRNam(ms, 0);
+ RetypeBag(ms, T_PREC + IMMUTABLE);
+ return ms;
+}
+
+
/****************************************************************************
**
*F * * * * * * * * * * * * * initialize package * * * * * * * * * * * * * * *
@@ -632,6 +837,8 @@ static StructGVarFunc GVarFuncs [] = {
GVAR_FUNC(SIZE_OBJ, 1, "obj"),
GVAR_FUNC(InitRandomMT, 1, "initstr"),
GVAR_FUNC(INT_STRING, 1, "string"),
+ GVAR_FUNC(MAKE_BITFIELDS, -1, "widths"),
+ GVAR_FUNC(BUILD_BITFIELDS, -2, "widths..."),
{ 0, 0, 0, 0, 0 }
};
@@ -645,6 +852,10 @@ static Int InitKernel (
StructInitInfo * module )
{
+ InitHandlerFunc(DoFieldSetter, "field-setter");
+ InitHandlerFunc(DoFieldGetter, "field-getter");
+ InitHandlerFunc(DoBooleanFieldSetter, "boolean-field-setter");
+ InitHandlerFunc(DoBooleanFieldGetter, "boolean-field-getter");
/* init filters and functions */
InitHdlrFuncsFromTable( GVarFuncs );
diff --git a/tst/testinstall/bitfields.tst b/tst/testinstall/bitfields.tst
new file mode 100644
index 00000000000..9e7dd7925ce
--- /dev/null
+++ b/tst/testinstall/bitfields.tst
@@ -0,0 +1,47 @@
+gap> START_TEST("bitfields.tst");
+
+# Test correct behaviour for a variety of numbers of fields
+# and all getters and setters.
+gap> for i in [1..2+GAPInfo.BytesPerVariable] do
+> bf := CallFuncList(MakeBitfields,[1..i]);
+> vals := List([1..i], j -> Random(0,2^j-1));
+> Add(vals, [1..i], 1);
+> x := CallFuncList(BuildBitfields,vals);
+> for j in [1..i] do
+> if bf.getters[j](x) <> vals[j+1] then
+> Print("Bad return from getter",i," ",j," ",x,"\n");
+> fi; od;
+> if bf.booleanGetters[1](x) <> (vals[2] = 1) then
+> Print("Bad return from Boolean Getter");
+> fi;
+> vals := List([1..i], j -> Random(0,2^j-1));
+> for j in [1..i] do
+> y := bf.setters[j](x, vals[j]);
+> if bf.getters[j](y) <> vals[j] then
+> Print("Bad return from getter and setter\n");
+> fi; od;
+> y := bf.booleanSetters[1](x,true);
+> z := bf.booleanSetters[1](x,false);
+> if (not bf.booleanGetters[1](y)) or bf.booleanGetters[1](z) then
+> Print("Bad results from boolean setter\n");
+> fi; od;
+
+#
+# Now test various error and extreme conditions
+#<
+gap> bf := MakeBitfields(1);
+rec( booleanGetters := [ function( data ) ... end ],
+ booleanSetters := [ function( data, val ) ... end ],
+ getters := [ function( data ) ... end ],
+ setters := [ function( data, val ) ... end ], widths := [ 1 ] )
+gap> bf.getters[1](Z(5));
+Error, Field getter: argument must be small integer
+gap> bf.setters[1](1, (1,2));
+Error, Field Setter: both arguments must be small integers
+gap> bf.setters[1]([],1);
+Error, Field Setter: both arguments must be small integers
+gap> BuildBitfields([1],Z(5));
+Error, Fields builder: values must be small integers
+gap> MakeBitfields(100);
+Error, MAKE_BITFIELDS: total widths too large
+gap> STOP_TEST("bitfields.tst", 1);