[gcc r17-2752] AVR: Add 64-bit fixed-point divisions to libgcc.

Georg-Johann Lay via Gcc-cvs <[email protected]>
Newsgroups gmane.comp.gcc.cvs
Message-ID <[email protected]>
https://gcc.gnu.org/g:eb738824d502d5f6da77451d5105091b8a7db92d

commit r17-2752-geb738824d502d5f6da77451d5105091b8a7db92d
Author: Georg-Johann Lay <[email protected]>
Date:   Tue Jul 28 15:50:14 2026 +0200

    AVR: Add 64-bit fixed-point divisions to libgcc.
    
    This adds 64-bit saturated fixed-point divisions to lib1funcs-fixed.S.
    The ordinary divisions are just aliases of the saturating ones.
    Rounding is towards zero.
    
    libgcc/
            * config/avr/t-avr (FUNCS_notiny): Add _udivuQ64_common, _udivuda3,
            _udivuta3, _udivudq3, _divda3, _divta3, _divdq3, _divQ64_tail.
            * config/avr/lib1funcs-fixed.S (__udivuta3, __usdivuta3)
            (__udivudq3, __usdivudq3, __udivuda3, __usdivuda3)
            (__udivuQ64_common, __divda3, __ssdivda3, __divta3, __ssdivta3)
            (__divdq3, __ssdivdq3, __divQ64_tail): New functions.
    gcc/testsuite/
            * gcc.target/avr/fx64-div-1.c: New test.
            * gcc.target/avr/fx64-div-2.c: New test.
            * gcc.target/avr/fx64-div-3.c: New test.
            * gcc.target/avr/fx64-mul.h: Rename to...
            * gcc.target/avr/fx64.h: ...this.
            * gcc.target/avr/fx64-mul-1.c: Same.
            * gcc.target/avr/fx64-mul-2.c: Same.

Diff:
---
 gcc/testsuite/gcc.target/avr/fx64-div-1.c          |  84 ++++++
 gcc/testsuite/gcc.target/avr/fx64-div-2.c          |  74 ++++++
 gcc/testsuite/gcc.target/avr/fx64-div-3.c          |  63 +++++
 gcc/testsuite/gcc.target/avr/fx64-mul-1.c          |   2 +-
 gcc/testsuite/gcc.target/avr/fx64-mul-2.c          |   2 +-
 .../gcc.target/avr/{fx64-mul.h => fx64.h}          |   6 +
 libgcc/config/avr/lib1funcs-fixed.S                | 289 +++++++++++++++++++--
 libgcc/config/avr/t-avr                            |   2 +
 8 files changed, 502 insertions(+), 20 deletions(-)

diff --git a/gcc/testsuite/gcc.target/avr/fx64-div-1.c b/gcc/testsuite/gcc.target/avr/fx64-div-1.c
new file mode 100644
index 000000000000..760171e7ebc1
--- /dev/null
+++ b/gcc/testsuite/gcc.target/avr/fx64-div-1.c
@@ -0,0 +1,84 @@
+/* { dg-do run { target { ! avr_tiny } } } */
+/* { dg-additional-options { -std=gnu99 -Os -mcall-prologues } } */
+
+// !!! Requires the fx64 <-> float conversions from AVR-LibC.
+
+#include "fx64.h"
+
+#define MK_TEST(fx)				    \
+  NI bool in_range_##fx (float x)		    \
+  {						    \
+    return x < fmax_##fx && x >= fmin_##fx;	    \
+  }						    \
+						    \
+  NI void test_div_##fx (float a, float b)	    \
+  {						    \
+    if (!in_range_##fx (a))			    \
+      return;					    \
+    if (!in_range_##fx (b))			    \
+      return;					    \
+    float f = b ? a / b : 0.0f;			    \
+    __asm volatile ("" : "+r" (f));		    \
+    fx##_t ax = (fx##_t) a;			    \
+    fx##_t bx = (fx##_t) b;			    \
+    fx##_t ab = ax / bx;			    \
+    if ((b == 0 && a < 0) || (b && f < fmin_##fx))  \
+      {						    \
+	if (ab != min_##fx)			    \
+	  exit (id_##fx + 1);			    \
+	return;					    \
+      }						    \
+    if ((b == 0 && a >= 0) || (b && f > fmax_##fx)) \
+      {						    \
+	if (ab != max_##fx)			    \
+	  exit (id_##fx + 2);			    \
+	return;					    \
+      }						    \
+    if (f != (float) ab)			    \
+      exit (id_##fx + 3);			    \
+  }
+
+MK_TEST (lk)
+MK_TEST (ulk)
+MK_TEST (llk)
+MK_TEST (ullk)
+MK_TEST (llr)
+MK_TEST (ullr)
+
+NI void test_div (float a, float b)
+{
+  test_div_lk (a, b);
+  test_div_ulk (a, b);
+
+  test_div_llk (a, b);
+  test_div_ullk (a, b);
+
+  test_div_llr (a, b);
+  test_div_ullr (a, b);
+}
+
+// Results / args must be representable as float, so no rounding occurs.
+// Non-overflow results must be representable as fixed, so no rounding occurs.
+const PROGMEM float fvals[] =
+  {
+    0.0,
+    +1.0, +2.0, +0.5, +0x1p10, +0x1p12, +0x1p-14,
+    -1.0, -2.0, -0.5, -0x1p10, -0x1p12, -0x1p-14,
+  };
+
+NI void test (void)
+{
+  for (uint8_t a = 0; a < ARRAY_SIZE (fvals); ++a)
+    for (uint8_t b = 0; b < ARRAY_SIZE (fvals); ++b)
+      {
+	float fa = pgm_read_float (&fvals[a]);
+	float fb = pgm_read_float (&fvals[b]);
+	test_div (fa, fb);
+      }
+}
+
+int main (void)
+{
+  test ();
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/avr/fx64-div-2.c b/gcc/testsuite/gcc.target/avr/fx64-div-2.c
new file mode 100644
index 000000000000..24cccf742ebf
--- /dev/null
+++ b/gcc/testsuite/gcc.target/avr/fx64-div-2.c
@@ -0,0 +1,74 @@
+/* { dg-do run { target { ! avr_tiny } } } */
+/* { dg-additional-options { -std=gnu99 -Os -mcall-prologues } } */
+
+// !!! Requires the fx64 <-> float conversions from AVR-LibC.
+
+#include "fx64.h"
+
+NI void test_lk (uint64_t a, float fb, uint64_t res)
+{
+  lk_t b = (lk_t) fb;
+  if (lkbits (a) / b != lkbits (res))
+    exit (id_lk + 4);
+}
+
+NI void test_ulk (uint64_t a, float fb, uint64_t res)
+{
+  ulk_t b = (ulk_t) fb;
+  if (ulkbits (a) / b != ulkbits (res))
+    exit (id_ulk + 4);
+}
+
+NI void test2_lk (uint64_t a, uint64_t b, uint64_t res)
+{
+  if (lkbits (a) / lkbits (b) != lkbits (res))
+    exit (id_lk + 5);
+}
+
+NI void test2_ulk (uint64_t a, uint64_t b, uint64_t res)
+{
+  if (ulkbits (a) / ulkbits (b) != ulkbits (res))
+    exit (id_ulk + 5);
+}
+
+NI void test (void)
+{
+  test_ulk (+0xaabbccddeeff7799, +0x11.0p0f, +0x0a0b0c0d0e0f0709);
+  test_lk  (-0x77bbccddeeff7799, +0x11.0p0f, -0x070b0c0d0e0f0709);
+  test_lk  (+0x77bbccddeeff7799, -0x11.0p0f, -0x070b0c0d0e0f0709);
+  test_lk  (-0x77bbccddeeff7799, -0x11.0p0f, +0x070b0c0d0e0f0709);
+  test_lk  (-0x7ffe02468ace2468, 2.0f, -0x3fff012345671234);
+  test_ulk (+0x7ffe02468ace2468, 2.0f, +0x3fff012345671234);
+  test_ulk (+0xc42eca8642eca955, 2.0f, +0x62176543217654aa);
+  test_lk  (-0x642eca8642eca955, 2.0f, -0x32176543217654aa);
+  test_ulk (+0xffffffffffffffff, 2.0f, +0x7fffffffffffffff);
+  test_lk  (-0x7fffffffffffffff, 2.0f, -0x3fffffffffffffff);
+  test_lk  (+0, 0.0f, SMAX);
+  test_lk  (+1, 0.0f, SMAX);
+  test_lk  (-1, 0.0f, SMIN);
+  test_ulk (+0, 0.0f, UMAX);
+  test_ulk (+1, 0.0f, UMAX);
+  test_lk  (SMAX - 1000, +0x0.fffp0f, SMAX);
+  test_lk  (SMIN + 1000, +0x0.fffp0f, SMIN);
+  test_lk  (SMAX - 1000, -0x0.fffp0f, SMIN);
+  test_lk  (SMIN + 1000, -0x0.fffp0f, SMAX);
+  test_ulk (UMAX - 1000, +0x0.fffp0f, UMAX);
+  test_ulk (UMAX - 1000, +0x0.fffp0f, UMAX);
+
+  test2_ulk (X80 + 1, X80, ulk_1);
+  test2_ulk (X80, X80 + 1, ulk_1 - 1);
+  test2_ulk (UMAX - 1, UMAX, ulk_1 - 1);
+  test2_lk  (SMAX, SMIN, -(lk_1 - 1));
+  test2_lk  (SMIN, SMAX, -lk_1);
+  test2_lk  (SMAX, SMAX, lk_1);
+  test2_lk  (SMAX, SMAX, lk_1);
+  test2_ulk (SMIN, SMIN, ulk_1);
+  test2_ulk (1, 1, ulk_1);
+  test2_lk  (1, 1, lk_1);
+}
+
+int main (void)
+{
+  test ();
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/avr/fx64-div-3.c b/gcc/testsuite/gcc.target/avr/fx64-div-3.c
new file mode 100644
index 000000000000..66434b3bb7e8
--- /dev/null
+++ b/gcc/testsuite/gcc.target/avr/fx64-div-3.c
@@ -0,0 +1,63 @@
+/* { dg-do run { target { ! avr_tiny } } } */
+/* { dg-additional-options { -std=gnu99 -Os -mcall-prologues } } */
+
+// !!! Requires the fx64 <-> float conversions from AVR-LibC.
+
+#include "fx64.h"
+
+#define MK_TEST(fx)						\
+  NI void test_div_##fx (uint64_t a, float fb, uint64_t r)	\
+  {								\
+    fx##_t ab = fx##bits (a) / (fx##_t) fb;			\
+    if (ab != fx##bits (r))					\
+      exit (id_##fx + 6);					\
+    return;							\
+  }
+
+MK_TEST (lk)
+MK_TEST (ulk)
+MK_TEST (llk)
+MK_TEST (ullk)
+
+NI void test1_div (uint64_t a, float fb, uint64_t r)
+{
+  test_div_ulk (a, fb, r);
+  test_div_ullk (a, fb, r);
+  if ((a & X80) == 0)
+    {
+      test_div_lk (a, fb, r);
+      test_div_llk (a, fb, r);
+    }
+}
+
+NI void test2_div (uint64_t a, float fb, uint64_t r)
+{
+  test_div_ulk (a, fb, r);
+  test_div_ullk (a, fb, r);
+  if ((r & X80) == 0)
+    {
+      test_div_lk (a, fb, r);
+      test_div_llk (a, fb, r);
+    }
+  else
+    {
+      test_div_lk (a, fb, SMAX);
+      test_div_llk (a, fb, SMAX);
+    }
+}
+
+NI void test (void)
+{
+  for (uint64_t a = (uint64_t) 0xff01 << (64 - 16); a; a >>= 1)
+    {
+      test1_div (a, 2.0f, a >> 1);
+      if ((a & X80) == 0)
+	test2_div (a, 0.5f, a << 1);
+    }
+}
+
+int main (void)
+{
+  test ();
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/avr/fx64-mul-1.c b/gcc/testsuite/gcc.target/avr/fx64-mul-1.c
index fb342b3cce78..68fd0dda0bbf 100644
--- a/gcc/testsuite/gcc.target/avr/fx64-mul-1.c
+++ b/gcc/testsuite/gcc.target/avr/fx64-mul-1.c
@@ -3,7 +3,7 @@
 
 // !!! Requires the fx64 <-> float conversions from AVR-LibC.
 
-#include "fx64-mul.h"
+#include "fx64.h"
 
 #define MK_TEST(fx)				    \
   NI bool in_range_##fx (float x)		    \
diff --git a/gcc/testsuite/gcc.target/avr/fx64-mul-2.c b/gcc/testsuite/gcc.target/avr/fx64-mul-2.c
index a679c010928f..266c7579da85 100644
--- a/gcc/testsuite/gcc.target/avr/fx64-mul-2.c
+++ b/gcc/testsuite/gcc.target/avr/fx64-mul-2.c
@@ -3,7 +3,7 @@
 
 // !!! Requires the fx64 <-> float conversions from AVR-LibC.
 
-#include "fx64-mul.h"
+#include "fx64.h"
 
 NI void test_mul (float fa, float fb, uint64_t res)
 {
diff --git a/gcc/testsuite/gcc.target/avr/fx64-mul.h b/gcc/testsuite/gcc.target/avr/fx64.h
similarity index 88%
rename from gcc/testsuite/gcc.target/avr/fx64-mul.h
rename to gcc/testsuite/gcc.target/avr/fx64.h
index 5c935d58c1a7..c7b968a901be 100644
--- a/gcc/testsuite/gcc.target/avr/fx64-mul.h
+++ b/gcc/testsuite/gcc.target/avr/fx64.h
@@ -35,6 +35,7 @@ typedef unsigned long long fract ullr_t;
 #define UMAX 0xffffffffffffffff
 #define SMAX 0x7fffffffffffffff
 #define SMIN 0x8000000000000000
+#define X80  0x8000000000000000
 
 // Values are in min <= x <= max.
 #define max_lk lkbits (SMAX)
@@ -52,6 +53,11 @@ typedef unsigned long long fract ullr_t;
 #define max_ullr ullrbits (UMAX)
 #define min_ullr ullrbits (0)
 
+#define lk_1   (1ull << __DA_FBIT__)
+#define ulk_1  (1ull << __UDA_FBIT__)
+#define llk_1  (1ull << __TA_FBIT__)
+#define ullk_1 (1ull << __UTA_FBIT__)
+
 #define id_lk   10
 #define id_ulk  20
 #define id_llk  30
diff --git a/libgcc/config/avr/lib1funcs-fixed.S b/libgcc/config/avr/lib1funcs-fixed.S
index f1b61e12e976..2996a5960eb1 100644
--- a/libgcc/config/avr/lib1funcs-fixed.S
+++ b/libgcc/config/avr/lib1funcs-fixed.S
@@ -1198,24 +1198,6 @@ DEFUN __mulQ64_work
 ENDF __mulQ64_work
 #endif /* L_mulQ64_work */
 
-#undef A0
-#undef A1
-#undef A2
-#undef A3
-#undef A4
-#undef A5
-#undef A6
-#undef A7
-
-#undef B0
-#undef B1
-#undef B2
-#undef B3
-#undef B4
-#undef B5
-#undef B6
-#undef B7
-
 #undef C0
 #undef C1
 #undef C2
@@ -1244,6 +1226,277 @@ ENDF __mulQ64_work
 
 .popsection ; .text.libgcc.mul64
 
+/*******************************************************
+    Fixed 64 / 64 saturated Divisions
+*******************************************************/
+
+;;; Cnt counts from 65 - BBB down to CStop = -FBIT.
+#define Cnt     r26
+#define CStop   r27
+#define BBB     24
+
+#if defined (L_udivuta3)
+;;; unsigned long [sat] accum
+DEFUN __udivuta3
+ENTRY __usdivuta3
+    push    r8
+    push    r9
+    push    r28
+    push    r29
+    ldi     CStop, -__UTA_FBIT__
+    XCALL   __udivuQ64_common
+    pop     r29
+    pop     r28
+    pop     r9
+    pop     r8
+    ret
+ENDF __udivuta3
+#endif /* L_udivuta3 */
+
+#if defined (L_udivudq3)
+;;; unsigned long long [sat] fract
+DEFUN __udivudq3
+ENTRY __usdivudq3
+    push    r8
+    push    r9
+    push    r28
+    push    r29
+    ldi     CStop, -__UDQ_FBIT__
+    XCALL   __udivuQ64_common
+    pop     r29
+    pop     r28
+    pop     r9
+    pop     r8
+    ret
+ENDF __udivudq3
+#endif /* L_udivudq3 */
+
+#if defined (L_udivuda3)
+;;; unsigned long [sat] accum
+DEFUN __udivuda3
+ENTRY __usdivuda3
+    push    r8
+    push    r9
+    push    r28
+    push    r29
+    ldi     CStop, -__UDA_FBIT__
+    XCALL   __udivuQ64_common
+    pop     r29
+    pop     r28
+    pop     r9
+    pop     r8
+    ret
+ENDF __udivuda3
+#endif /* L_udivuda3 */
+
+#if defined (L_udivuQ64_common)
+;;; Remainder
+#define M0  A5
+#define M1  A6
+#define M2  A7
+#define M3  28
+#define M4  M3+1
+#define M5  0
+#define M6  M5+1
+#define M7  9
+
+;;; Quotient
+#define Q0  30
+#define Q1  Q0+1
+#define Q2  8
+#define Q3  A0
+#define Q4  A1
+#define Q5  A2
+#define Q6  A3
+#define Q7  A4
+
+;;; Perform unsigned saturated 64-bit fixed-point division.
+;;; Rounds towards zero.  Division by 0 is saturated to 0xff..
+;;; R18  = R18  / R10
+;;; A[8] = A[8] / B[8]
+;;; CStop R27 is -FBIT.
+;;; No callee-saved regs are saved.
+;;; T is not touched.
+;;;
+;;; Registers overlap as follows:
+;;;                     A7  A6  A5  A4  A3  A2  A1  A0
+;;; M7  M6  M5  M4  M3  M2  M1  M0  Q7  Q6  Q5  Q4  Q3  Q2  Q1  Q0
+;;; r9  r1  r0  r29 r28 r25 r24 r23 r22 r21 r20 r19 r18 r8  r31 r30
+;;; This means the algorithm has a head start of BBB = 24 bits.
+;;; (32 bits is too much to pass the tests.)
+DEFUN __udivuQ64_common
+    ldi     Cnt, 65 - BBB
+    ;; M[7..3] = 0.
+    ;; Q[2..0] = 0.
+    sub     M5, M5              ; CLC
+    ;; M6 = 0 already.
+    wmov    r8, M5              ; M7, Q2
+    wmov    M3, M5              ; M4, M3
+    wmov    Q0, M5              ; Q1, Q0
+    rjmp .Lenter
+
+.Loop:
+    ;; Shift one more bit of A into M from the low end.
+    ;; When A's bits are exhausted, shift in 0s.
+    ROL  M0         $ rol  M1       $ rol  M2       $ rol  M3
+    rol  M4         $ rol  M5       $ rol  M6       $ rol  M7
+
+    ;; When M overflows then B will always fit.
+    brcs    .LsubB
+    CP   M0, B0     $ cpc  M1, B1   $ cpc  M2, B2   $ cpc  M3, B3
+    cpc  M4, B4     $ cpc  M5, B5   $ cpc  M6, B6   $ cpc  M7, B7
+    brlo    .LshiftQ
+
+.LsubB:
+    SUB  M0, B0     $ sbc  M1, B1   $ sbc  M2, B2   $ sbc  M3, B3
+    sbc  M4, B4     $ sbc  M5, B5   $ sbc  M6, B6   $ sbc  M7, B7
+
+    SEC
+    ROL  Q0
+    skip
+.LshiftQ:
+    LSL  Q0         $ rol  Q1       $ rol  Q2
+.Lenter:                                            $ rol  Q3
+    rol  Q4         $ rol  Q5       $ rol  Q6       $ rol  Q7
+
+    dec     Cnt
+    ;; Cnt >= 0: Feed one more bit of A (plus a final 0 for latency) into M.
+    brpl .Loop
+
+    cpse Cnt, CStop
+    ;; Cnt < 0: Feed 0s into M.
+    brcc .Loop
+
+    ;; Done
+    clr     __zero_reg__
+    ;; When quotient 1s drop out of Q, then it's overflow.
+    brcs .Loverflow
+
+    ;; Move result into place.
+    mov  A7, Q7     $ mov  A6, Q6   $ mov  A5, Q5   $ mov  A4, Q4
+    mov  A3, Q3     $ mov  A2, Q2   $                 wmov A0, Q0
+    ret
+
+.Loverflow:
+    ;; A = 0xff..
+    XJMP    __sbc_8
+ENDF __udivuQ64_common
+
+#undef Q0
+#undef Q1
+#undef Q2
+#undef Q3
+#undef Q4
+#undef Q5
+#undef Q6
+#undef Q7
+
+#undef M0
+#undef M1
+#undef M2
+#undef M3
+#undef M4
+#undef M5
+#undef M6
+#undef M7
+#endif /* L_udivuQ64_common */
+
+;;; Perform signed saturated 64-bit fixed-point division.
+;;; Division by 0 is saturated according to the sign of the dividend.
+;;; Rounds towards zero.
+
+#if defined (L_divda3)
+;;; signed long [sat] accum
+DEFUN __divda3
+ENTRY __ssdivda3
+    ;; Save R8, R9, B and Y.
+    do_prologue_saves 12
+    ldi     CStop, -__DA_FBIT__
+    XJMP    __divQ64_tail
+ENDF __divda3
+#endif /* L_divda3 */
+
+#if defined (L_divta3)
+;;; signed long long [sat] accum
+DEFUN __divta3
+ENTRY __ssdivta3
+    ;; Save R8, R9, B and Y.
+    do_prologue_saves 12
+    ldi     CStop, -__TA_FBIT__
+    XJMP    __divQ64_tail
+ENDF __divta3
+#endif /* L_divta3 */
+
+#if defined (L_divdq3)
+;;; signed long long [sat] fract
+DEFUN __divdq3
+ENTRY __ssdivdq3
+    ;; Save R8, R9, B and Y.
+    do_prologue_saves 12
+    ldi     CStop, -__DQ_FBIT__
+    XJMP    __divQ64_tail
+ENDF __divdq3
+#endif /* L_divdq3 */
+
+#if defined (L_divQ64_tail)
+DEFUN __divQ64_tail
+    mov     __tmp_reg__, A7
+    eor     __tmp_reg__, B7
+    bst     __tmp_reg__, 7
+
+    .call_if_neg  A7, __negdi2          ; A = |A|
+    .call_if_neg  B7, __negdi2_r10      ; B = |B|
+    XCALL   __udivuQ64_common           ; A /= B as unsigned
+
+    ;; r30.7 = result sign
+    bld     r30, 7
+
+    ;; Handle overflow.
+    tst     A7
+    ;; In the negative result case, A = 0x80.. is no overflow,
+    ;; but treating it as such keeps the value unchanged.
+    brmi .Lsaturate
+
+    ;; Handle result sign.
+    brtc 9f
+    XCALL   __negdi2
+
+9:  do_epilogue_restores 12
+
+.Lsaturate:
+    ;; r30.7 = 0 -> 0x7f..
+    ;; r30.7 = 1 -> 0x80..
+    cpi     r30, 0x80
+    ;; C = 1 -> 0x7f..
+    ;; C = 0 -> 0x80..
+    XCALL   __sbc_8
+    subi    A7, 0x80
+    rjmp    9b
+ENDF __divQ64_tail
+#endif /* L_divQ64_tail */
+
+#undef Cnt
+#undef CStop
+#undef BBB
+
+#undef A0
+#undef A1
+#undef A2
+#undef A3
+#undef A4
+#undef A5
+#undef A6
+#undef A7
+
+#undef B0
+#undef B1
+#undef B2
+#undef B3
+#undef B4
+#undef B5
+#undef B6
+#undef B7
+
 #endif /* ! AVR_TINY */
 
 /*******************************************************
diff --git a/libgcc/config/avr/t-avr b/libgcc/config/avr/t-avr
index 657c61a56664..edd0e8f86983 100644
--- a/libgcc/config/avr/t-avr
+++ b/libgcc/config/avr/t-avr
@@ -100,6 +100,8 @@ FUNCS_notiny += \
 	_mulda3 _multa3 _muldq3 \
 	_muluQ64_tail _mulQ64_work \
 	_divsa3 _udivusa3 \
+	_udivuda3 _udivuta3 _udivudq3 _udivuQ64_common \
+	_divda3 _divta3 _divdq3 _divQ64_tail \
 	_clr_8 \
 	_ssneg_4 _ssneg_8 \
 	_ssabs_4 _ssabs_8 \
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.