On Wed, Feb 27, 2013 at 2:59 AM, Kenneth Zadeck <zad...@naturalbridge.com> wrote: > This patch contains a large number of the changes requested by Richi. It > does not contain any of the changes that he requested to abstract the > storage layer. That suggestion appears to be quite unworkable.
I of course took this claim as a challenge ... with the following result. It is of course quite workable ;) The attached patch implements the core wide-int class and three storage models (fixed size for things like plain HWI and double-int, variable size similar to how your wide-int works and an adaptor for the double-int as contained in trees). With that you can now do HOST_WIDE_INT wi_test (tree x) { // template argument deduction doesn't do the magic we want it to do // to make this kind of implicit conversions work // overload resolution considers this kind of conversions so we // need some magic that combines both ... but seeding the overload // set with some instantiations doesn't seem to be possible :/ // wide_int<> w = x + 1; wide_int<> w; w += x; w += 1; // template argument deduction doesn't deduce the return value type, // not considering the template default argument either ... // w = wi (x) + 1; // we could support this by providing rvalue-to-lvalue promotion // via a traits class? // otoh it would lead to sub-optimal code anyway so we should // make the result available as reference parameter and only support // wide_int <> res; add (res, x, 1); ? w = wi (x).operator+<wide_int<> >(1); wide_int<>::add(w, x, 1); return w.to_hwi (); } we are somewhat limited with C++ unless we want to get really fancy. Eventually providing operator+ just doesn't make much sense for generic wide-int combinations (though then the issue is its operands are no longer commutative which I think is the case with your wide-int or double-int as well - they don't suport "1 + wide_int" for obvious reasons). So there are implementation design choices left undecided. Oh, and the operation implementations are crap (they compute nonsense). But you should get the idea. Richard.
#include "config.h" #include "system.h" #include "coretypes.h" #include "hwint.h" #include "tree.h" /* ??? wide-int should probably use HOST_WIDEST_FAST_INT as storage, not HOST_WIDE_INT. Yeah, we could even template on that ... */ /* Fixed-length embedded storage. wi_embed<2> is double-int, wi_embed<1> is a plain HOST_WIDE_INT. Can be used for small fixed-(minimum)-size calculations on hosts that have no suitable integer type. */ template <unsigned sz> class wi_embed { private: HOST_WIDE_INT s[sz]; public: void construct () {} HOST_WIDE_INT* storage() { return s; } const HOST_WIDE_INT* storage() const { return s; } unsigned len() const { return sz; } void set_len(unsigned l) { gcc_checking_assert (l <= sz); } }; /* Fixed maximum-length embedded storage but variable dynamic size. */ //#define MAXSZ (4 * (MAX_MODE_INT_SIZE / HOST_BITS_PER_WIDE_INT)) #define MAXSZ 8 template <unsigned max_sz> class wi_embed_var { private: unsigned len_; HOST_WIDE_INT s[max_sz]; public: void construct () { len_ = 0; } HOST_WIDE_INT* storage() { return s; } const HOST_WIDE_INT* storage() const { return s; } unsigned len() const { return len_; } void set_len(unsigned l) { len_ = l; } }; /* The wide-int class. Defaults to variable-length storage (alternatively use a typedef to avoid the need to use wide_int <>). */ template <class S = wi_embed_var<MAXSZ> > class wide_int; /* Avoid constructors / destructors to make sure this is a C++04 POD. */ /* Basic wide_int class. The storage model allows for rvalue storage abstraction avoiding copying from for example tree or RTX and to avoid the need of explicit construction for integral arguments of up to HWI size. A storage model needs to provide the following methods: - construct (), default-initialize the storage - unsigned len () const, the size of the storage in HWI quantities - const HOST_WIDE_INT *storage () const, return a pointer to read-only HOST_WIDE_INT storage of size len (). - HOST_WIDE_INT *storage (), return a pointer to writable HOST_WIDE_INT storage of size len (). This method is optional. - void set_len (unsigned l), adjust the size of the storage to at least l HWI words. Conversions of wide_int _to_ tree or RTX or HWI are explicit. Conversions to wide_int happen with overloads to the global function template wi () or via wide_int_traits specializations. */ /* ??? With mixed length operations there are encoding issues for signed vs. unsigned numbers. The easiest encoding is to say wide-ints are always signed which means that -1U needs the MSB of the wide-int storage as zero which means an extra word with zeros. The sign-bit of a wide-int is then always storage()[len() & (1 << (HOST_BITS_PER_WIDE_INT - 1))]. */ template <class S> class wide_int : private S { /* Allow access to the storage object of operands. */ template <class S2> friend class wide_int; public: typedef wide_int<S> WideInt_t; void construct (const S& s) { static_cast<S&>(*this) = s; } /* ??? We'd really want the following member templates to behave as a set of overloads available for each wide-int storage model. Like WideInt_t& operator (const wide_int <wi_tree_int_cst>&); WideInt_t& operator (const wide_int <wi_embed>&); WideInt_t& operator (const wide_int <wi_embed_var>&); forwarding to a common implementation. But that's not extensible from the outside (tree bits should reside in tree.h), nor is it possible without explicitely writing down all overloads and explicitely forwarding to the common implementation. Thus we require the user to explicitely convert to wide-int via a function overload (see below). */ template <class S2> WideInt_t& operator=(const wide_int <S2>&); /* Self-modify operators have obvious result types. */ template <class T> WideInt_t& operator+=(const T&); /* Note the result must support an lvalue storage. */ /* Automatic promotion is difficult but could be done via another traits class wi_promote_lvalue<S1, S2>::S - of course then storage models would need to know about each others. */ // otoh it would lead to sub-optimal code anyway so we should // make the result available as reference parameter and only support // wide_int <> res; add (res, x, 1); ? template <class S1, class T> wide_int<S1> operator+(const T&); template <class T1, class T2> static wide_int<S>& add (wide_int<S>& res, const T1 &, const T2 &); HOST_WIDE_INT to_hwi () const { return this->storage()[0]; } }; /* This traits class is to provide a means of accessing T as an rvalue wide-int. This allows us to omit the explicit conversion to wide_int <storage> in most places and allow extending the wide-int interface outside of wide-int.h. Extend this by providing a constructor that builds a wrapper around type T and operator-> that provides access to a wide_int<> representing it. */ template <class T> class wi_traits; /* Wrap a wide_int as itself. */ template <class S> class wi_traits <wide_int <S> > { public: typedef wide_int <S> wi_t; wi_traits(const wide_int <S> &w_) : w (w_) {} const wi_t* operator->() const { return &w; } private: const wi_t &w; }; /* Wrap any integral type up to the size of a (unsigned) HWI as wide_int <wi_embed <1> >. ??? The following only handles 'int', handling the rest of the suitable integer types via a separate helper trait is left as an excercise for the reader. */ template <> class wi_traits <int> { public: typedef wide_int <wi_embed <1> > wi_t; wi_traits(HOST_WIDE_INT hwi) { wi_embed <1> ws; ws.construct (); ws.storage()[0] = hwi; w.construct (ws); } wi_t* operator->() { return &w; } private: wi_t w; }; /* wide-int operations. To avoid code-bloat the actual workers can be trivially outlined by giving them a non-template interface working on HOST_WIDE_INT * arrays. */ template <class S> template <class S2> wide_int<S>& wide_int<S>::operator=(const wide_int<S2>&b) { unsigned i; this->set_len (b.len()); for (i = 0; i < b.len(); ++i) this->storage()[i] = b.storage()[i]; for (; i < this->len(); ++i) this->storage()[i] = 0; return *this; } template <class S> template <class T> wide_int<S>& wide_int<S>::operator+=(const T &b_) { wi_traits<T> b(b_); /* Compute a += b. */ unsigned i; if (b->len () > this->len ()) this->set_len (b->len ()); for (i = 0; i < this->len (); ++i) this->storage()[i] += b->storage()[i]; return *this; } template <class S> template <class S1, class T> wide_int<S1> wide_int<S>::operator+(const T &w) { /* Compute a + b */ wide_int<S1> res; // ??? Initialization from *this not possible via implicit // use of operator= */ // wide_int<S1> res = *this; res = *this; res += w; return res; } template <class S> template <class T1, class T2> wide_int<S>& wide_int<S>::add(wide_int<S>& res, const T1&a_, const T2&b_) { wi_traits<T1> a(a_); wi_traits<T2> b(b_); res.set_len (a->len () > b->len () ? a->len() : b->len ()); for (unsigned i = 0; i < res.len (); ++i) res.storage()[i] = a->storage()[i] + b->storage()[i]; return res; } // ----- for tree.h class wi_tree_int_cst { tree cst; public: void construct (tree c) { cst = c; } const HOST_WIDE_INT *storage() const { return reinterpret_cast <HOST_WIDE_INT *>(&TREE_INT_CST (cst)); } unsigned len() const { return 2; } }; template <> class wi_traits <tree> { public: typedef wide_int <wi_tree_int_cst> wi_t; wi_traits(tree t) { wi_tree_int_cst ws; ws.construct (t); w.construct (ws); } wi_t* operator->() { return &w; } private: wi_t w; }; union T { wide_int<> w; unsigned x; } test; wide_int<wi_tree_int_cst> wi (tree t) { wide_int<wi_tree_int_cst> w; wi_tree_int_cst ws; ws.construct (t); w.construct (ws); return w; } wide_int<wi_embed <1> > wi (HOST_WIDE_INT hwi) { wide_int<wi_embed <1> > w; wi_embed <1> ws; ws.construct (); ws.storage()[0] = hwi; w.construct (ws); return w; } HOST_WIDE_INT wi_test (tree x) { // template argument deduction doesn't do the magic we want it to do // to make this kind of implicit conversions work // overload resolution considers this kind of conversions so we // need some magic that combines both ... but seeding the overload // set with some instantiations doesn't seem to be possible :/ // wide_int<> w = x + 1; wide_int<> w; w += x; w += 1; // template argument deduction doesn't deduce the return value type, // not considering the template default argument either ... // w = wi (x) + 1; // we could support this by providing rvalue-to-lvalue promotion // via a traits class? // otoh it would lead to sub-optimal code anyway so we should // make the result available as reference parameter and only support // wide_int <> res; add (res, x, 1); ? w = wi (x).operator+<wide_int<> >(1); wide_int<>::add(w, x, 1); return w.to_hwi (); }