From 3eed2bfc0536bf5a53de2f7e4c9fad0ebc22c793 Mon Sep 17 00:00:00 2001 From: Steve Linton Date: Tue, 22 Aug 2017 11:58:39 +0100 Subject: [PATCH] Bitfields feature. Includes tests and documentation. 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. --- doc/ref/integers.xml | 15 +++ doc/ref/makedocreldata.g | 1 + lib/bitfields.gd | 119 +++++++++++++++++++ lib/bitfields.gi | 2 + lib/read3.g | 1 + lib/read5.g | 1 + src/intfuncs.c | 211 ++++++++++++++++++++++++++++++++++ tst/testinstall/bitfields.tst | 47 ++++++++ 8 files changed, 397 insertions(+) create mode 100644 lib/bitfields.gd create mode 100644 lib/bitfields.gi create mode 100644 tst/testinstall/bitfields.tst 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);