Skip to content

Commit

Permalink
Bitfields feature. Includes tests and documentation.
Browse files Browse the repository at this point in the history
Bitfields provide a fast (by GAP standards) and tidy way of viewing an immediate integer
as a set of bitfields. Handy mainly for compact data structures.
  • Loading branch information
stevelinton committed Sep 11, 2017
1 parent 71eb99e commit e0f26d9
Show file tree
Hide file tree
Showing 8 changed files with 397 additions and 0 deletions.
15 changes: 15 additions & 0 deletions doc/ref/integers.xml
Original file line number Diff line number Diff line change
Expand Up @@ -174,5 +174,20 @@ random integers and random choices from lists.
<#Include Label="RandomSource">

</Section>
<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -->
<Section Label="Bitfields">
<Heading>Bitfields</Heading>
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 (<E>inter alia</E>) it has different limitations on 32-bit and 64-bit
architectures.

<#Include Label="MakeBitfields">
<#Include Label="BuildBitfields">

</Section>

</Chapter>

1 change: 1 addition & 0 deletions doc/ref/makedocreldata.g
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
119 changes: 119 additions & 0 deletions lib/bitfields.gd
Original file line number Diff line number Diff line change
@@ -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(<width>[, <width>[, <width>...]]])
## <#GAPDoc Label="MakeBitfields">
## <ManSection>
## <Func Name="MakeBitfields" Arg='width....'/>
##
## <Description>
##
## 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
## <C>GAPInfo.BytesPerVariable</C> which is 8 on 64-bits and 4 on 32.
##
##
## The return value when <M>n</M> widths are given is a record whose fields are
## <List>
## <Mark><C>widths</C></Mark> <Item>a copy of the arguments, for convenience,</Item>
## <Mark><C>getters</C></Mark> <Item> a list of <M>n</M> functions of one
## argument each of which extracts one of the fields from an immediate integer</Item>
## <Mark><C>setters</C></Mark> <Item> a list of <M>n</M> functions each taking
## two arguments: a packed value and a new value for one of its fields and
## returning a new packed value. The
## <M>i</M>th function returned the new packed value in which the <M>i</M>th
## field has been replaced by the new value.
## Note that this does NOT modify the original packed value.</Item>
##
## </List>
##
## 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.
## <List> <Mark><C>booleanGetters</C></Mark> <Item>if the <M>i</M>th position of
## this list is set, it contains a function which extracts the <M>i</M>th
## field (which will have width one) and returns <C>true</C> if it contains 1
## and <C>false</C> if it contains 0</Item>
## <Mark><C>booleanSetters</C></Mark> <Item>if the <M>i</M>th position of
## this list is set, it contains a function of two arguments. The first
## argument is a packed value, the second is <C>true</C> or <C>false</C>. It returns a
## new packed value in which the <M>i</M>th field is set to 1 if the second
## argument was <C>true</C> and 0 if it was <C>false</C>. Behaviour for any
## other value is undefined.</Item></List>
## </Description>
## </ManSection>
## <#/GAPDoc>

DeclareGlobalFunction( "MakeBitfields" );

##
#############################################################################
##
#F BuildBitfields( <widths>[, <val1>, [<val2>...]])
## <#GAPDoc Label="BuildBitfields">
## <ManSection>
## <Func Name="BuildBitfields" Arg='widths,....'/>
## <Description>
##
## This function takes one or more argument. It's first argument is a list
## of field widths, as found in the <C>widths</C> entry of a record returned
## by <C>MakeBitfields</C>. 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");

##
## <Example><![CDATA[
## gap> bf := MakeBitfields(1,2,3);
## rec( builder := function( val1, val2, val3 ) ... 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 := BitfieldBuilder(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
## ]]></Example>
## </Description>
## </ManSection>
## <#/GAPDoc>
##



#############################################################################
##
#E

2 changes: 2 additions & 0 deletions lib/bitfields.gi
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
InstallGlobalFunction(MakeBitfields, MAKE_BITFIELDS);
InstallGlobalFunction(BuildBitfields, BUILD_BITFIELDS);
1 change: 1 addition & 0 deletions lib/read3.g
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ ReadLib( "extrset.gd" );
ReadLib( "extuset.gd" );

ReadLib( "dict.gd" );
ReadLib( "bitfields.gd" );

ReadLib( "mapping.gd" );
ReadLib( "mapphomo.gd" );
Expand Down
1 change: 1 addition & 0 deletions lib/read5.g
Original file line number Diff line number Diff line change
Expand Up @@ -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" );

Expand Down
211 changes: 211 additions & 0 deletions src/intfuncs.c
Original file line number Diff line number Diff line change
Expand Up @@ -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, "<field setter>", 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, "<field getter>", 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, "<boolean field setter>",
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, "<boolean field getter>",
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 * * * * * * * * * * * * * * *
Expand All @@ -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 }

};
Expand All @@ -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 );
Expand Down
Loading

0 comments on commit e0f26d9

Please sign in to comment.