public inbox for gdb-cvs@sourceware.org
help / color / mirror / Atom feed
* [binutils-gdb] gdb: LoongArch: Implement the push_dummy_call gdbarch method
@ 2022-07-07  9:10 Tiezhu Yang
  0 siblings, 0 replies; only message in thread
From: Tiezhu Yang @ 2022-07-07  9:10 UTC (permalink / raw)
  To: gdb-cvs

https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=88de583569400f6c81caa6f943d1290d134ea48a

commit 88de583569400f6c81caa6f943d1290d134ea48a
Author: Tiezhu Yang <yangtiezhu@loongson.cn>
Date:   Thu Jul 7 14:33:19 2022 +0800

    gdb: LoongArch: Implement the push_dummy_call gdbarch method
    
    According to "Procedure Calling Convention" in "LoongArch ELF ABI
    specification" [1], implement the push_dummy_call gdbarch method
    as clear as possible.
    
    [1] https://loongson.github.io/LoongArch-Documentation/LoongArch-ELF-ABI-EN.html#_procedure_calling_convention
    
    Signed-off-by: Tiezhu Yang <yangtiezhu@loongson.cn>

Diff:
---
 gdb/loongarch-tdep.c | 596 +++++++++++++++++++++++++++++++++++++++++++++++++++
 gdb/loongarch-tdep.h |   2 +
 2 files changed, 598 insertions(+)

diff --git a/gdb/loongarch-tdep.c b/gdb/loongarch-tdep.c
index 76480ce6c94..3add71b2a09 100644
--- a/gdb/loongarch-tdep.c
+++ b/gdb/loongarch-tdep.c
@@ -452,6 +452,599 @@ static const struct frame_unwind loongarch_frame_unwind = {
   /*.prev_arch	   =*/nullptr,
 };
 
+static void
+pass_in_gar (struct regcache *regcache, unsigned int gar, const gdb_byte *val)
+{
+  unsigned int regnum = LOONGARCH_ARG_REGNUM - gar + LOONGARCH_A0_REGNUM;
+  regcache->cooked_write (regnum, val);
+}
+
+static void
+pass_in_far (struct regcache *regcache, unsigned int far, const gdb_byte *val)
+{
+  unsigned int regnum = LOONGARCH_ARG_REGNUM - far + LOONGARCH_BADV_REGNUM + 1;
+  regcache->cooked_write (regnum, val);
+}
+
+static __attribute__((aligned(16))) gdb_byte buf[1024] = { 0 };
+static gdb_byte *addr = buf;
+
+static void
+pass_on_stack (struct regcache *regcache, const gdb_byte *val, size_t len, int align)
+{
+  align = align_up (align, 8);
+  if (align > 16)
+    align = 16;
+
+  CORE_ADDR align_addr = (CORE_ADDR) addr;
+  align_addr = align_up (align_addr, align);
+  addr = (gdb_byte *) align_addr;
+  memcpy (addr, val, len);
+  addr += len;
+}
+
+static unsigned int fixed_point_members = 0;
+static unsigned int floating_point_members = 0;
+static bool first_member_is_fixed_point = false;
+
+static void
+compute_struct_member (struct type *type)
+{
+  for (int i = 0; i < type->num_fields (); i++)
+    {
+      struct type *field_type = check_typedef (type->field (i).type ());
+
+      if (field_type->code () == TYPE_CODE_INT
+	  || field_type->code () == TYPE_CODE_BOOL
+	  || field_type->code () == TYPE_CODE_CHAR
+	  || field_type->code () == TYPE_CODE_RANGE
+	  || field_type->code () == TYPE_CODE_ENUM
+	  || field_type->code () == TYPE_CODE_PTR)
+      {
+	fixed_point_members++;
+
+	if (floating_point_members == 0)
+	  first_member_is_fixed_point = true;
+      }
+      else if (field_type->code () == TYPE_CODE_FLT)
+	floating_point_members++;
+      else if (field_type->code () == TYPE_CODE_STRUCT)
+	compute_struct_member (field_type);
+      else if (field_type->code () == TYPE_CODE_COMPLEX)
+	floating_point_members += 2;
+    }
+}
+
+/* Implement the push_dummy_call gdbarch method.  */
+
+static CORE_ADDR
+loongarch_push_dummy_call (struct gdbarch *gdbarch,
+			   struct value *function,
+			   struct regcache *regcache,
+			   CORE_ADDR bp_addr,
+			   int nargs,
+			   struct value **args,
+			   CORE_ADDR sp,
+			   function_call_return_method return_method,
+			   CORE_ADDR struct_addr)
+{
+  int regsize = register_size (gdbarch, 0);
+  unsigned int gar = LOONGARCH_ARG_REGNUM;
+  unsigned int far = LOONGARCH_ARG_REGNUM;
+
+  if (return_method != return_method_normal)
+    pass_in_gar (regcache, gar--, (gdb_byte *) &struct_addr);
+
+  addr = buf;
+  for (int i = 0; i < nargs; i++)
+    {
+      struct value *arg = args[i];
+      const gdb_byte *val = value_contents (arg).data ();
+      struct type *type = check_typedef (value_type (arg));
+      size_t len = TYPE_LENGTH (type);
+      int align = type_align (type);
+      enum type_code code = type->code ();
+
+      switch (code)
+	{
+	case TYPE_CODE_INT:
+	case TYPE_CODE_BOOL:
+	case TYPE_CODE_CHAR:
+	case TYPE_CODE_RANGE:
+	case TYPE_CODE_ENUM:
+	case TYPE_CODE_PTR:
+	  {
+	    /* integer or pointer type is passed in GAR.
+	     * If no GAR is available, it’s passed on the stack.
+	     * When passed in registers or on the stack,
+	     * the unsigned integer scalars are zero-extended to GRLEN bits,
+	     * and the signed integer scalars are sign-extended.  */
+	  if (type->is_unsigned ())
+	    {
+              ULONGEST data = extract_unsigned_integer (val, len, BFD_ENDIAN_LITTLE);
+	      if (gar > 0)
+		pass_in_gar (regcache, gar--, (gdb_byte *) &data);
+	      else
+		pass_on_stack (regcache, (gdb_byte *) &data, len, align);
+            }
+	  else
+	    {
+	      LONGEST data = extract_signed_integer (val, len, BFD_ENDIAN_LITTLE);
+	      if (gar > 0)
+		pass_in_gar (regcache, gar--, (gdb_byte *) &data);
+	      else
+		pass_on_stack (regcache, (gdb_byte *) &data, len, align);
+            }
+	  }
+	  break;
+	case TYPE_CODE_FLT:
+	  if (len == 2 * regsize)
+	    {
+	      /* long double type is passed in a pair of GAR,
+	       * with the low-order GRLEN bits in the lower-numbered register
+	       * and the high-order GRLEN bits in the higher-numbered register.
+	       * If exactly one register is available,
+	       * the low-order GRLEN bits are passed in the register
+	       * and the high-order GRLEN bits are passed on the stack.
+	       * If no GAR is available, it’s passed on the stack.  */
+	      if (gar >= 2)
+		{
+		  pass_in_gar (regcache, gar--, val);
+		  pass_in_gar (regcache, gar--, val + regsize);
+		}
+	      else if (gar == 1)
+		{
+		  pass_in_gar (regcache, gar--, val);
+		  pass_on_stack (regcache, val + regsize, len - regsize, align);
+		}
+	      else
+		{
+		  pass_on_stack (regcache, val, len, align);
+		}
+	    }
+	  else
+	    {
+	      /* The other floating-point type is passed in FAR.
+	       * If no FAR is available, it’s passed in GAR.
+	       * If no GAR is available, it’s passed on the stack.  */
+	      if (far > 0)
+		  pass_in_far (regcache, far--, val);
+	      else if (gar > 0)
+		  pass_in_gar (regcache, gar--, val);
+	      else
+		  pass_on_stack (regcache, val, len, align);
+	    }
+	  break;
+	case TYPE_CODE_STRUCT:
+	  {
+	    fixed_point_members = 0;
+	    floating_point_members = 0;
+	    first_member_is_fixed_point = false;
+	    compute_struct_member (type);
+
+	    if (len > 0 && len <= regsize)
+	      {
+		/* The structure has only fixed-point members.  */
+		if (fixed_point_members > 0 && floating_point_members == 0)
+		  {
+		    /* If there is an available GAR,
+		     * the structure is passed through the GAR by value passing;
+		     * If no GAR is available, it’s passed on the stack.  */
+		    if (gar > 0)
+		      pass_in_gar (regcache, gar--, val);
+		    else
+		      pass_on_stack (regcache, val, len, align);
+		  }
+		/* The structure has only floating-point members.  */
+		else if (fixed_point_members == 0 && floating_point_members > 0)
+		  {
+		    /* One floating-point member.
+		     * The argument is passed in a FAR.
+		     * If no FAR is available, the value is passed in a GAR.
+		     * if no GAR is available, the value is passed on the stack.  */
+		    if (floating_point_members == 1)
+		      {
+			if (far > 0)
+			  pass_in_far (regcache, far--, val);
+			else if (gar > 0)
+			  pass_in_gar (regcache, gar--, val);
+			else
+			  pass_on_stack (regcache, val, len, align);
+		      }
+		    /* Two floating-point members.
+		     * The argument is passed in a pair of available FAR,
+		     * with the low-order float member bits in the lower-numbered FAR
+		     * and the high-order float member bits in the higher-numbered FAR.
+		     * If the number of available FAR is less than 2, it’s passed in a GAR,
+		     * and passed on the stack if no GAR is available.  */
+		    else if (floating_point_members == 2)
+		      {
+			if (far >= 2)
+			  {
+			    pass_in_far (regcache, far--, val);
+			    pass_in_far (regcache, far--, val + align);
+			  }
+			else if (gar > 0)
+			  {
+			    pass_in_gar (regcache, gar--, val);
+			  }
+			else
+			  {
+			    pass_on_stack (regcache, val, len, align);
+			  }
+		      }
+		  }
+		/* The structure has both fixed-point and floating-point members.  */
+		else if (fixed_point_members > 0 && floating_point_members > 0)
+		  {
+		    /* One float member and multiple fixed-point members.
+		     * If there are available GAR, the structure is passed in a GAR,
+		     * and passed on the stack if no GAR is available.  */
+		    if (floating_point_members == 1 && fixed_point_members > 1)
+		      {
+			if (gar > 0)
+			  pass_in_gar (regcache, gar--, val);
+			else
+			  pass_on_stack (regcache, val, len, align);
+		      }
+		    /* One float member and only one fixed-point member.
+		     * If one FAR and one GAR are available,
+		     * the floating-point member of the structure is passed in the FAR,
+		     * and the fixed-point member of the structure is passed in the GAR.
+		     * If no floating-point register but one GAR is available, it’s passed in GAR;
+		     * If no GAR is available, it’s passed on the stack.  */
+		    else if (floating_point_members == 1 && fixed_point_members == 1)
+		      {
+			if (far > 0 && gar > 0)
+			  {
+			    if (first_member_is_fixed_point == false)
+			      {
+				pass_in_far (regcache, far--, val);
+				pass_in_gar (regcache, gar--, val + align);
+			      }
+			    else
+			      {
+				pass_in_gar (regcache, gar--, val);
+				pass_in_far (regcache, far--, val + align);
+			      }
+			  }
+			else
+			  {
+			    if (gar > 0)
+			      pass_in_gar (regcache, gar--, val);
+			    else
+			      pass_on_stack (regcache, val, len, align);
+			  }
+		      }
+		  }
+	      }
+	    else if (len > regsize && len <= 2 * regsize)
+	      {
+		/* Only fixed-point members.  */
+		if (fixed_point_members > 0 && floating_point_members == 0)
+		  {
+		    /* The argument is passed in a pair of available GAR,
+		     * with the low-order bits in the lower-numbered GAR
+		     * and the high-order bits in the higher-numbered GAR.
+		     * If only one GAR is available,
+		     * the low-order bits are in the GAR
+		     * and the high-order bits are on the stack,
+		     * and passed on the stack if no GAR is available.  */
+		    if (gar >= 2)
+		      {
+			pass_in_gar (regcache, gar--, val);
+			pass_in_gar (regcache, gar--, val + regsize);
+		      }
+		    else if (gar == 1)
+		      {
+			pass_in_gar (regcache, gar--, val);
+			pass_on_stack (regcache, val + regsize, len - regsize, align);
+		      }
+		    else
+		      {
+			pass_on_stack (regcache, val, len, align);
+		      }
+		  }
+		/* Only floating-point members.  */
+		else if (fixed_point_members == 0 && floating_point_members > 0)
+		  {
+		    /* The structure has one long double member
+		     * or one double member and two adjacent float members
+		     * or 3-4 float members.
+		     * The argument is passed in a pair of available GAR,
+		     * with the low-order bits in the lower-numbered GAR
+		     * and the high-order bits in the higher-numbered GAR.
+		     * If only one GAR is available,
+		     * the low-order bits are in the GAR
+		     * and the high-order bits are on the stack,
+		     * and passed on the stack if no GAR is available.  */
+		    if ((len == 16 && floating_point_members == 1)
+			 || (len == 16 && floating_point_members == 3)
+			 || (len == 12 && floating_point_members == 3)
+			 || (len == 16 && floating_point_members == 4))
+		      {
+			if (gar >= 2)
+			  {
+			    pass_in_gar (regcache, gar--, val);
+			    pass_in_gar (regcache, gar--, val + regsize);
+			  }
+			else if (gar == 1)
+			  {
+			    pass_in_gar (regcache, gar--, val);
+			    pass_on_stack (regcache, val + regsize, len - regsize, align);
+			  }
+			else
+			  {
+			    pass_on_stack (regcache, val, len, align);
+			  }
+		      }
+		    /* The structure with two double members
+		     * is passed in a pair of available FAR,
+		     * with the low-order bits in the lower-numbered FAR
+		     * and the high-order bits in the higher-numbered FAR.
+		     * If no a pair of available FAR,
+		     * it’s passed in a pair of available GAR,
+		     * with the low-order bits in the lower-numbered GAR
+		     * and the high-order bits in the higher-numbered GAR.
+		     * If only one GAR is available,
+		     * the low-order bits are in the GAR
+		     * and the high-order bits are on stack,
+		     * and passed on the stack if no GAR is available.
+		     * A structure with one double member and one float member is same.  */
+		    else if ((len == 16 && floating_point_members == 2)
+			     || (len == 12 && floating_point_members == 2))
+		      {
+			if (far >= 2)
+			  {
+			    pass_in_far (regcache, far--, val);
+			    pass_in_far (regcache, far--, val + regsize);
+			  }
+			else if (gar >= 2)
+			  {
+			    pass_in_gar (regcache, gar--, val);
+			    pass_in_gar (regcache, gar--, val + regsize);
+			  }
+			else if (gar == 1)
+			  {
+			    pass_in_gar (regcache, gar--, val);
+			    pass_on_stack (regcache, val + regsize, len - regsize, align);
+			  }
+			else
+			  {
+			    pass_on_stack (regcache, val, len, align);
+			  }
+		      }
+		  }
+		/* Both fixed-point and floating-point members.  */
+		else if (fixed_point_members > 0 && floating_point_members > 0)
+		  {
+		    /* The structure has one floating-point member and only one fixed-point member.  */
+		    if (floating_point_members == 1 && fixed_point_members == 1)
+		      {
+			/* If one FAR and one GAR are available,
+			 * the floating-point member of the structure is passed in the FAR,
+			 * and the fixed-point member of the structure is passed in the GAR;
+			 * If no floating-point registers but two GARs are available,
+			 * it’s passed in the two GARs;
+			 * If only one GAR is available,
+			 * the low-order bits are in the GAR
+			 * and the high-order bits are on the stack;
+			 * And it’s passed on the stack if no GAR is available.  */
+			if (far > 0 && gar > 0)
+			  {
+			    if (first_member_is_fixed_point == false)
+			      {
+				pass_in_far (regcache, far--, val);
+				pass_in_gar (regcache, gar--, val + regsize);
+			      }
+			    else
+			      {
+				pass_in_gar (regcache, gar--, val);
+				pass_in_far (regcache, far--, val + regsize);
+			      }
+			  }
+			else if (far == 0 && gar >= 2)
+			  {
+			    pass_in_gar (regcache, gar--, val);
+			    pass_in_gar (regcache, gar--, val + regsize);
+			  }
+			else if (far == 0 && gar == 1)
+			  {
+			    pass_in_gar (regcache, gar--, val);
+			    pass_on_stack (regcache, val + regsize, len - regsize, align);
+			  }
+			else if (far == 0 && gar == 0)
+			  {
+			    pass_on_stack (regcache, val, len, align);
+			  }
+		      }
+		    else
+		      {
+			/* The argument is passed in a pair of available GAR,
+			 * with the low-order bits in the lower-numbered GAR
+			 * and the high-order bits in the higher-numbered GAR.
+			 * If only one GAR is available,
+			 * the low-order bits are in the GAR
+			 * and the high-order bits are on the stack,
+			 * and passed on the stack if no GAR is available.  */
+			if (gar >= 2)
+			  {
+			    pass_in_gar (regcache, gar--, val);
+			    pass_in_gar (regcache, gar--, val + regsize);
+			  }
+			else if (gar == 1)
+			  {
+			    pass_in_gar (regcache, gar--, val);
+			    pass_on_stack (regcache, val + regsize, len - regsize, align);
+			  }
+			else
+			  {
+			    pass_on_stack (regcache, val, len, align);
+			  }
+		      }
+		  }
+	      }
+	    else if (len > 2 * regsize)
+	      {
+	        /* It’s passed by reference and are replaced in the argument list with the address.
+		 * If there is an available GAR, the reference is passed in the GAR,
+		 * and passed on the stack if no GAR is available.  */
+		sp = align_down (sp - len, 16);
+		write_memory (sp, val, len);
+
+		if (gar > 0)
+		  pass_in_gar (regcache, gar--, (const gdb_byte *) &sp);
+		else
+		  pass_on_stack (regcache, (const gdb_byte*) &sp, len, regsize);
+	      }
+	  }
+	  break;
+	case TYPE_CODE_UNION:
+	  /* Union is passed in GAR or stack.  */
+	  if (len > 0 && len <= regsize)
+	    {
+	      /* The argument is passed in a GAR,
+	       * or on the stack by value if no GAR is available.  */
+	      if (gar > 0)
+		pass_in_gar (regcache, gar--, val);
+	      else
+		pass_on_stack (regcache, val, len, align);
+	    }
+	  else if (len > regsize && len <= 2 * regsize)
+	    {
+	      /* The argument is passed in a pair of available GAR,
+	       * with the low-order bits in the lower-numbered GAR
+	       * and the high-order bits in the higher-numbered GAR.
+	       * If only one GAR is available,
+	       * the low-order bits are in the GAR
+	       * and the high-order bits are on the stack.
+	       * The arguments are passed on the stack when no GAR is available.  */
+	      if (gar >= 2)
+		{
+		  pass_in_gar (regcache, gar--, val);
+		  pass_in_gar (regcache, gar--, val + regsize);
+		}
+	      else if (gar == 1)
+		{
+		  pass_in_gar (regcache, gar--, val);
+		  pass_on_stack (regcache, val + regsize, len - regsize, align);
+		}
+	      else
+		{
+		  pass_on_stack (regcache, val, len, align);
+		}
+	    }
+	  else if (len > 2 * regsize)
+	    {
+	      /* It’s passed by reference and are replaced in the argument list with the address.
+	       * If there is an available GAR, the reference is passed in the GAR,
+	       * and passed on the stack if no GAR is available.  */
+	      sp = align_down (sp - len, 16);
+	      write_memory (sp, val, len);
+
+	      if (gar > 0)
+		pass_in_gar (regcache, gar--, (const gdb_byte *) &sp);
+	      else
+		pass_on_stack (regcache, (const gdb_byte*) &sp, len, regsize);
+	    }
+	  break;
+	case TYPE_CODE_COMPLEX:
+	  {
+	    struct type *target_type = check_typedef (TYPE_TARGET_TYPE (type));
+	    size_t target_len = TYPE_LENGTH (target_type);
+
+	    if (target_len < regsize)
+	      {
+		/* The complex with two float members
+		 * is passed in a pair of available FAR,
+		 * with the low-order float member bits in the lower-numbered FAR
+		 * and the high-order float member bits in the higher-numbered FAR.
+		 * If the number of available FAR is less than 2, it’s passed in a GAR,
+		 * and passed on the stack if no GAR is available.  */
+		if (far >= 2)
+		  {
+		    pass_in_far (regcache, far--, val);
+		    pass_in_far (regcache, far--, val + align);
+		  }
+		else if (gar > 0)
+		  {
+		    pass_in_gar (regcache, gar--, val);
+		  }
+		else
+		  {
+		    pass_on_stack (regcache, val, len, align);
+		  }
+	      }
+	    else if (target_len == regsize)
+	      {
+		/* The complex with two double members
+		 * is passed in a pair of available FAR,
+		 * with the low-order bits in the lower-numbered FAR
+		 * and the high-order bits in the higher-numbered FAR.
+		 * If no a pair of available FAR,
+		 * it’s passed in a pair of available GAR,
+		 * with the low-order bits in the lower-numbered GAR
+		 * and the high-order bits in the higher-numbered GAR.
+		 * If only one GAR is available,
+		 * the low-order bits are in the GAR
+		 * and the high-order bits are on stack,
+		 * and passed on the stack if no GAR is available.  */
+		{
+		  if (far >= 2)
+		    {
+		      pass_in_far (regcache, far--, val);
+		      pass_in_far (regcache, far--, val + align);
+		    }
+		  else if (gar >= 2)
+		    {
+		      pass_in_gar (regcache, gar--, val);
+		      pass_in_gar (regcache, gar--, val + align);
+		    }
+		  else if (gar == 1)
+		    {
+		      pass_in_gar (regcache, gar--, val);
+		      pass_on_stack (regcache, val + align, len - align, align);
+		    }
+		  else
+		    {
+		      pass_on_stack (regcache, val, len, align);
+		    }
+		}
+	      }
+	    else if (target_len == 2 * regsize)
+	      {
+		/* The complex with two long double members
+		 * is passed by reference and are replaced in the argument list with the address.
+	         * If there is an available GAR, the reference is passed in the GAR,
+	         * and passed on the stack if no GAR is available.  */
+		sp = align_down (sp - len, 16);
+		write_memory (sp, val, len);
+
+		if (gar > 0)
+		  pass_in_gar (regcache, gar--, (const gdb_byte *) &sp);
+		else
+		  pass_on_stack (regcache, (const gdb_byte*) &sp, regsize, regsize);
+	      }
+	  }
+	  break;
+	default:
+	  break;
+	}
+    }
+
+  if (addr > buf)
+    {
+      sp -= addr - buf;
+      sp = align_down (sp, 16);
+      write_memory (sp, buf, addr - buf);
+    }
+
+  regcache_cooked_write_unsigned (regcache, LOONGARCH_RA_REGNUM, bp_addr);
+  regcache_cooked_write_unsigned (regcache, LOONGARCH_SP_REGNUM, sp);
+
+  return sp;
+}
+
 /* Implement the return_value gdbarch method.  */
 
 static enum return_value_convention
@@ -644,6 +1237,9 @@ loongarch_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
   /* Finalise the target description registers.  */
   tdesc_use_registers (gdbarch, tdesc, std::move (tdesc_data));
 
+  /* Functions handling dummy frames.  */
+  set_gdbarch_push_dummy_call (gdbarch, loongarch_push_dummy_call);
+
   /* Return value info  */
   set_gdbarch_return_value (gdbarch, loongarch_return_value);
 
diff --git a/gdb/loongarch-tdep.h b/gdb/loongarch-tdep.h
index acf0191fd65..672bc2b80de 100644
--- a/gdb/loongarch-tdep.h
+++ b/gdb/loongarch-tdep.h
@@ -39,6 +39,8 @@ enum
   LOONGARCH_PC_REGNUM = 33,		/* Program Counter.  */
   LOONGARCH_BADV_REGNUM = 34,		/* Bad Vaddr for Addressing Exception.  */
   LOONGARCH_LINUX_NUM_GREGSET = 45,	/* 32 GPR, ORIG_A0, PC, BADV, RESERVED 10.  */
+  LOONGARCH_ARG_REGNUM = 8,            /* r4-r11: general-purpose argument registers.
+					  f0-f7: floating-point argument registers.  */
 };
 
 /* Register set definitions.  */


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2022-07-07  9:10 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-07-07  9:10 [binutils-gdb] gdb: LoongArch: Implement the push_dummy_call gdbarch method Tiezhu Yang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).