Re: [Fortran] half-cycle trig functions and atan[d] fixes

2024-01-23 Thread Steve Kargl
On Tue, Jan 23, 2024 at 01:37:54PM +0200, Janne Blomqvist wrote:
> 
> > - If I get this right, to take one example, the Fortran front-end will emit 
> > a call to gfortran_acospi_r4(), libgfortran provides this as a wrapper 
> > calling acospif(), which is called either from libm or from libgfortran. 
> > This is different from other math library functions, like ACOS() where the 
> > acosf() call is generated directly from the front-end (and then the 
> > implementation comes either from libm or from libgfortran). Why not follow 
> > our usual way of doing things?
> 
> Good point, that's what we've done in c99_functions.c in libgfortran.
> We should probably do this so we can skip jumping via libgfortran when
> libm implements these directly.
> 

Hopefully, FX sees this as my emails to gmail bounce.

I don't see how this can work or I don't understand symbol versioning
in libraries.

If an OS does not supply cospi() in its libm, then gfortran will
fallback to a cospi() in libgfortran.  With the indirection I 
currently implement, cospi() is not in the symbol map of libgfortran
and _gfortran_cospi_r4() will use the libgfortran version.  Now, if
the OS adds cospi() to libm and it's in libm's symbol map, then the
cospi() used by gfortran depends on the search order of the loaded
libraries.  Consider on FreeBSD, I have

% nm --dynamic /lib/libm.so.5 | grep cospi
00025b60 T cospi@@FBSD_1.7
00025fe0 T cospif@@FBSD_1.7
0002e230 T cospil@@FBSD_1.7

% nm --dynamic work/lib/libgfortran.so.5 | grep cospi
002b1e60 T _gfortran_acospi_r10@@GFORTRAN_14
002b4590 T _gfortran_acospi_r16@@GFORTRAN_14
002b1d80 T _gfortran_acospi_r4@@GFORTRAN_14
002b1df0 T _gfortran_acospi_r8@@GFORTRAN_14
002b1ea0 T _gfortran_cospi_r10@@GFORTRAN_14
002b46d0 T _gfortran_cospi_r16@@GFORTRAN_14
002b1dc0 T _gfortran_cospi_r4@@GFORTRAN_14
002b1e30 T _gfortran_cospi_r8@@GFORTRAN_14
 U cospi@FBSD_1.7
 U cospif@FBSD_1.7
 U cospil@FBSD_1.7

The FE generates code for _gfortran_cospi_rXX.  If FreeBSD
adds say acospif() and libgfortran also exported, then 
how does the linker choose between acospif@@FBSD_1.7 and
acospif@@GFORTRAN_14?.

-- 
Steve


Re: GSoc Topics

2024-01-23 Thread Gaurang Aswal
Thanks, I'll check them out.

On Wed, 24 Jan, 2024, 03:52 Martin Jambor,  wrote:

> Hello,
>
> We are delighted you found contributing to GCC interesting.  GCC has
> applied to be part of GSoC 2024 but of course selected organizations
> have not been announced yet.
>
> On Fri, Jan 12 2024, Gaurang Aswal via Gcc wrote:
> > Hey I am Gaurang Aswal a 4th year B.E. Computer Science student from BITS
> > Goa, India and wanted some info and insights on the Fortran projects and
> I
> > am interested in working on them.
>
> While the list of suggested projects at
> https://gcc.gnu.org/wiki/SummerOfCode is undergoing review for 2024, I
> think the Fortran ones will not change much.  Please look over them, try
> to figure out what they would entail and which you'd like best.  In this
> reply I have also CCed the Fortran mailing list, people there might help
> you decide which Fortan project would be the best for you.
>
>
> > I have basic knowledge of C/C++ and I
> > have completed my basic computer science courses in the same language,
> > which included Object Oriented Programming, Data Structures and
> Algorithms,
> > Computer Architecture and Operating Systems. I would like to keep in
> touch
> > and want to know how to proceed working on the topics.
>
> Contributing to the compiler also requires some rudimentary theoretical
> background in compilers, at the very least understanding of the concept
> of Intermediate Representation (IR), often also called Intermediate
> Language (IL).  Googling either of the two terms should help you to find
> a lot of material to familiarize yourself with it.
>
> Please also look again at the "Before you apply" section of the idea
> page https://gcc.gnu.org/wiki/SummerOfCode#Before_you_apply and make
> sure you are able to build, install and test GCC and then have it
> generate dumps and step through some function during compilation.
>
> Also, feel free to ask for help here with any specific GCC development
> issues you may encounter.
>
> Good luck,
>
> Martin
>


Re: GSoc Topics

2024-01-23 Thread Martin Jambor
Hello,

We are delighted you found contributing to GCC interesting.  GCC has
applied to be part of GSoC 2024 but of course selected organizations
have not been announced yet.

On Fri, Jan 12 2024, Gaurang Aswal via Gcc wrote:
> Hey I am Gaurang Aswal a 4th year B.E. Computer Science student from BITS
> Goa, India and wanted some info and insights on the Fortran projects and I
> am interested in working on them.

While the list of suggested projects at
https://gcc.gnu.org/wiki/SummerOfCode is undergoing review for 2024, I
think the Fortran ones will not change much.  Please look over them, try
to figure out what they would entail and which you'd like best.  In this
reply I have also CCed the Fortran mailing list, people there might help
you decide which Fortan project would be the best for you.


> I have basic knowledge of C/C++ and I
> have completed my basic computer science courses in the same language,
> which included Object Oriented Programming, Data Structures and Algorithms,
> Computer Architecture and Operating Systems. I would like to keep in touch
> and want to know how to proceed working on the topics.

Contributing to the compiler also requires some rudimentary theoretical
background in compilers, at the very least understanding of the concept
of Intermediate Representation (IR), often also called Intermediate
Language (IL).  Googling either of the two terms should help you to find
a lot of material to familiarize yourself with it.

Please also look again at the "Before you apply" section of the idea
page https://gcc.gnu.org/wiki/SummerOfCode#Before_you_apply and make
sure you are able to build, install and test GCC and then have it
generate dumps and step through some function during compilation.

Also, feel free to ask for help here with any specific GCC development
issues you may encounter.

Good luck,

Martin


[PATCH] Fortran: passing of optional dummies to elemental procedures [PR113377]

2024-01-23 Thread Harald Anlauf
Dear all,

here's the second part of a series for the treatment of missing
optional arguments passed to optional dummies, now fixing the
case that the latter procedures are elemental.  Adjustments
were necessary when the missing dummy has the VALUE attribute.

I factored the code for the treatment of VALUE, hoping that the
monster loop in gfc_conv_procedure_call will become slightly
easier to overlook.

Regtested on x86_64-pc-linux-gnu.  OK for mainline?

Thanks,
Harald

From bd97af4225bf596260610ea37241ee503842435e Mon Sep 17 00:00:00 2001
From: Harald Anlauf 
Date: Tue, 23 Jan 2024 21:23:42 +0100
Subject: [PATCH] Fortran: passing of optional dummies to elemental procedures
 [PR113377]

gcc/fortran/ChangeLog:

	PR fortran/113377
	* trans-expr.cc (conv_dummy_value): New.
	(gfc_conv_procedure_call): Factor code for handling dummy arguments
	with the VALUE attribute in the scalar case into conv_dummy_value().
	Reuse and adjust for calling elemental procedures.

gcc/testsuite/ChangeLog:

	PR fortran/113377
	* gfortran.dg/optional_absent_10.f90: New test.
---
 gcc/fortran/trans-expr.cc | 198 +---
 .../gfortran.dg/optional_absent_10.f90| 219 ++
 2 files changed, 333 insertions(+), 84 deletions(-)
 create mode 100644 gcc/testsuite/gfortran.dg/optional_absent_10.f90

diff --git a/gcc/fortran/trans-expr.cc b/gcc/fortran/trans-expr.cc
index 128add47516..0fac0523670 100644
--- a/gcc/fortran/trans-expr.cc
+++ b/gcc/fortran/trans-expr.cc
@@ -6075,6 +6075,105 @@ conv_cond_temp (gfc_se * parmse, gfc_expr * e, tree cond)
 }


+/* Helper function for the handling of (currently) scalar dummy variables
+   with the VALUE attribute.  Argument parmse should already be set up.  */
+static void
+conv_dummy_value (gfc_se * parmse, gfc_expr * e, gfc_symbol * fsym,
+		  vec *& optionalargs)
+{
+  tree tmp;
+
+  gcc_assert (fsym && fsym->attr.value && !fsym->attr.dimension);
+
+  /* Absent actual argument for optional scalar dummy.  */
+  if (e == NULL && fsym->attr.optional && !fsym->attr.dimension)
+{
+  /* For scalar arguments with VALUE attribute which are passed by
+	 value, pass "0" and a hidden argument for the optional status.  */
+  if (fsym->ts.type == BT_CHARACTER)
+	{
+	  /* Pass a NULL pointer for an absent CHARACTER arg and a length of
+	 zero.  */
+	  parmse->expr = null_pointer_node;
+	  parmse->string_length = build_int_cst (gfc_charlen_type_node, 0);
+	}
+  else
+	parmse->expr = fold_convert (gfc_sym_type (fsym),
+ integer_zero_node);
+  vec_safe_push (optionalargs, boolean_false_node);
+
+  return;
+}
+
+  /* gfortran argument passing conventions:
+ actual arguments to CHARACTER(len=1),VALUE
+ dummy arguments are actually passed by value.
+ Strings are truncated to length 1.  */
+  if (gfc_length_one_character_type_p (>ts))
+{
+  if (e->expr_type == EXPR_CONSTANT
+	  && e->value.character.length > 1)
+	{
+	  e->value.character.length = 1;
+	  gfc_conv_expr (parmse, e);
+	}
+
+  tree slen1 = build_int_cst (gfc_charlen_type_node, 1);
+  gfc_conv_string_parameter (parmse);
+  parmse->expr = gfc_string_to_single_character (slen1, parmse->expr,
+		 e->ts.kind);
+  /* Truncate resulting string to length 1.  */
+  parmse->string_length = slen1;
+}
+
+  if (fsym->attr.optional
+  && fsym->ts.type != BT_CLASS
+  && fsym->ts.type != BT_DERIVED)
+{
+  /* F2018:15.5.2.12 Argument presence and
+	 restrictions on arguments not present.  */
+  if (e->expr_type == EXPR_VARIABLE
+	  && e->rank == 0
+	  && (gfc_expr_attr (e).allocatable
+	  || gfc_expr_attr (e).pointer))
+	{
+	  gfc_se argse;
+	  tree cond;
+	  gfc_init_se (, NULL);
+	  argse.want_pointer = 1;
+	  gfc_conv_expr (, e);
+	  cond = fold_convert (TREE_TYPE (argse.expr), null_pointer_node);
+	  cond = fold_build2_loc (input_location, NE_EXPR,
+  logical_type_node,
+  argse.expr, cond);
+	  vec_safe_push (optionalargs,
+			 fold_convert (boolean_type_node, cond));
+	  /* Create "conditional temporary".  */
+	  conv_cond_temp (parmse, e, cond);
+	}
+  else if (e->expr_type != EXPR_VARIABLE
+	   || !e->symtree->n.sym->attr.optional
+	   || (e->ref != NULL && e->ref->type != REF_ARRAY))
+	vec_safe_push (optionalargs, boolean_true_node);
+  else
+	{
+	  tmp = gfc_conv_expr_present (e->symtree->n.sym);
+	  if (e->ts.type != BT_CHARACTER && !e->symtree->n.sym->attr.value)
+	parmse->expr
+	  = fold_build3_loc (input_location, COND_EXPR,
+ TREE_TYPE (parmse->expr),
+ tmp, parmse->expr,
+ fold_convert (TREE_TYPE (parmse->expr),
+	   integer_zero_node));
+
+	  vec_safe_push (optionalargs,
+			 fold_convert (boolean_type_node, tmp));
+	}
+}
+}
+
+
+
 /* Generate code for a procedure call.  Note can return se->post != NULL.
If se->direct_byref is set then se->expr contains the return parameter.
Return nonzero, if the call has alternate specifiers.

Re: [PATCH] openmp, fortran: Add Fortran support for indirect clause on the declare target directive

2024-01-23 Thread Tobias Burnus

Kwok Cheung Yeung wrote:
This patch adds support for the indirect clause on the OpenMP 'declare 
target' directive in Fortran. As with the C and C++ front-ends, this 
applies the 'omp declare target indirect' attribute on affected 
function declarations. The C test cases have also been translated to 
Fortran where appropriate.


Okay for mainline?


LGTM – can you also update the following libgomp.texi entry?

@item @code{indirect} clause in @code{declare target} @tab P @tab Only C 
and C++


Thanks,

Tobias



Re: [Fortran] half-cycle trig functions and atan[d] fixes

2024-01-23 Thread Steve Kargl
On Tue, Jan 23, 2024 at 01:37:54PM +0200, Janne Blomqvist wrote:
> On Tue, Jan 23, 2024 at 11:09 AM FX Coudert  wrote:
> >
> > Hi Steve,
> 
> Hello, long time no see.

Time is short and we're all busy with life, but it is nice to see
familiar names!

> 
> > Thanks for the patch. I’ll take time to do a proper review, but after a 
> > first read I had the following questions:
> >
> > - "an OS's libm may/will contain cospi(), etc.”: do those functions conform 
> > to any standard? Are there plans to implement them outside FreeBSD at this 
> > point?
> 
> acospi, asinpi, atan2pi, atanpi, cospi, sinpi, tanpi are all in IEEE
> 754-2019, and are slated to be part of C23. I would assume actively
> developed libm's will eventually catch up and implement them.
> 
> > - If I get this right, to take one example, the Fortran front-end will emit 
> > a call to gfortran_acospi_r4(), libgfortran provides this as a wrapper 
> > calling acospif(), which is called either from libm or from libgfortran. 
> > This is different from other math library functions, like ACOS() where the 
> > acosf() call is generated directly from the front-end (and then the 
> > implementation comes either from libm or from libgfortran). Why not follow 
> > our usual way of doing things?
> 
> Good point, that's what we've done in c99_functions.c in libgfortran.
> We should probably do this so we can skip jumping via libgfortran when
> libm implements these directly.
> 
> > - On most targets, cospi() and friends are not available. Therefore, we end 
> > up doing the fallback (with limited precision as you noted) but with a lot 
> > of indirection. We could generate that code directly in the front-end, 
> > couldn’t we?
> 
> The frontend generally doesn't know what the target libm implements,
> hence it's better to just generate the call, and if necessary have a
> barebones implementation in libgfortran if libm doesn't implement it
> properly.
> 

I suppose I could add a gfc_conv_trigpi() to trans-intrinsic.c,
which could be used emit the function calls directly.  Give me
until Saturday to look at the issue.

I've finally understood what Harald has been trying to tell
me about the MPFR version issue in bugzilla, so I need to 
revamp simplification.

-- 
Steve


Re: [Fortran] half-cycle trig functions and atan[d] fixes

2024-01-23 Thread Steve Kargl
On Tue, Jan 23, 2024 at 06:40:27AM -0800, Steve Kargl wrote:
> On Tue, Jan 23, 2024 at 10:08:43AM +0100, FX Coudert wrote:
> > Hi Steve,
> > 
> > Thanks for the patch. I’ll take time to do a proper review, but
> > after a first read I had the following questions:
> > 
> > - "an OS's libm may/will contain cospi(), etc.”: do those functions
> > conform to any standard? Are there plans to implement them outside
> >FreeBSD at this point?
> 
> Yes.  cospi, sinpi, and tanpi are in IEEE754-2008.  These are
> also in ISO/IEC TS 18661-4 along with acospi, asinpi, and atanpi,
> which I believe where merged into C23 (see n3096.pdf).  I've

I haven't checked.

> checked if atan2pi is in C23, but it is in F2023.
> 
> AFAIK, FreeBSD's libm is the only OS that contains cospi, sinpi,
> and tanpi.  The arc functions haven't been written/committed,
> because something like cospi(x) = cos(x) / pi with pi split
> into hi-lo parts is good enough.

Whoops. acospi(x) = acos(x) / pi.  The fallback implements this
as acospi(x) = acos(x) * invpihi + acos(x) * invpilo with invpihi
the leading digits(x)/2 bits of 1/pi and invpilo the trailing
digits(x) of 1/pi.

For most libm's, acos(x) with |x| <= 1 have good numerical accuracy
and handle subnormal, |x| > 1, +-inf, and NaN by setting appropriate
exceptions.  With FreeBSD and exhaustive testing in the interval, I see

% tlibm acos -f -x 0x1p-9 -X 1 -PED
Interval tested for acosf: [0.00195312,1]
   ulp <= 0.5:  96.432%  72803871 |  96.432%  72803871
0.5 <  ulp <  0.6:  3.110%   2348017 |  99.542%  75151888
0.6 <  ulp <  0.7:  0.419%316134 |  99.961%  75468022
0.7 <  ulp <  0.8:  0.039% 29450 | 100.000%  75497472
Max ulp: 0.796035 at 4.99814630e-01

Exhaustive testing of the fallback routine for acospi(x) gives

   program foo

   implicit none

   integer n
   real(4) f4, x, xm
   real(8) f8, u, v

   u = -1
   x = scale(1., -9)
   do
  f4 = acospi(x)
  f8 = acospi(real(x,8))
  n = exponent(f8)
  v = abs(f8 - f4)
  v = scale(v, digits(f4) - n)
  if (v > u) then
 u = v
 xm = x
  end if
  x = nearest(x, 2.)
  if (x > 1) exit
   end do

   print *, u, acospi(xm), acospi(real(xm,8))

   end program foo

% gfcx -o z -O a.f90 && ./z
   1.9551682807505131   0.337791681  0.33779173955822828 

so a max ulp of 1.955.  Hopefully, OS libm's will catch up and
provide an implementation that gets the extra 1+ bit of precision.

Admittedly, the fallback routines for cospi(), sinpi(), and
tanpi() could be better, but much slower.  For now, I do for
example,

float
cospif (float x)
{
   return cosf (x * pihi_f + x * pilo_f);
}

A better routine would be,

/* cospi(x) = cos(pi*x) = cos(pi*n+pi*r) = +-cos(pi*r) with 0 <= r < 1i
   and n integer. */
float
cospif (float x)
{
   int s;
   float ax, n, r;
   ax = fabsf(ax);
   if (ax > 0x1p23) {
  return 1;
   } else {
  /* FreeBSD uses bit twiddling.  See
 https://cgit.freebsd.org/src/tree/lib/msun/src/s_cospif.c  */
  n = floor(ax);  /* integer part */
  r = ax - n; /* remainder */
  s = floor(n/2) * 2 - n == 0 : 1 : -1;  /* even n? */ 
  return s * cosf (r * pi);
   }
}

> > - On systems where libquadmath is used for _Float128, does
> > libquadmath contain the implementation of the q() variants for
> > those functions?
> 
> AFAIK, libquadmath does not have the half-cycle functions.  I
> wrote the function trig_fallback2.F90 to deal with REAL(16) (and
> maybe REAL17).
> 
> > - If I get this right, to take one example, the Fortran front-end
> > will emit a call to gfortran_acospi_r4(), libgfortran provides this
> > as a wrapper calling acospif(), which is called either from libm
> > or from libgfortran.
> 
> Yes, that is correct.  I tried to install __builtin_acospif in
> trans-intrinsic to generate a direct call to acospif, but that
> led to many hours/days of frustration.  I gave up.
> 
> > This is different from other math library functions, like ACOS()
> > where the acosf() call is generated directly from the front-end
> > (and then the implementation comes either from libm or from
> > libgfortran). Why not follow our usual way of doing things?
> 
> I gave up on that approach when I got some real obscure error
> about some math function which I did not touch. 
> 

I tried adding

DEFINE_MATH_BUILTIN_C (COSPI,  "cospi",   0)
or
DEFINE_MATH_BUILTIN (COSPI,  "cospi",   0)
or
OTHER_BUILTIN (COSPI, "cospi",  1, false)  (and variations).

to mathbuiltins.def but this led to some obscure error message
about some other unrelated intrinsic subprogram.

-- 
Steve


Re: [Fortran] half-cycle trig functions and atan[d] fixes

2024-01-23 Thread Steve Kargl
On Tue, Jan 23, 2024 at 10:08:43AM +0100, FX Coudert wrote:
> Hi Steve,
> 
> Thanks for the patch. I’ll take time to do a proper review, but
> after a first read I had the following questions:
> 
> - "an OS's libm may/will contain cospi(), etc.”: do those functions
> conform to any standard? Are there plans to implement them outside
>FreeBSD at this point?

Yes.  cospi, sinpi, and tanpi are in IEEE754-2008.  These are
also in ISO/IEC TS 18661-4 along with acospi, asinpi, and atanpi,
which I believe where merged into C23 (see n3096.pdf).  I've
checked if atan2pi is in C23, but it is in F2023.

AFAIK, FreeBSD's libm is the only OS that contains cospi, sinpi,
and tanpi.  The arc functions haven't been written/committed,
because something like cospi(x) = cos(x) / pi with pi split
into hi-lo parts is good enough.

> - On systems where libquadmath is used for _Float128, does
> libquadmath contain the implementation of the q() variants for
> those functions?

AFAIK, libquadmath does not have the half-cycle functions.  I
wrote the function trig_fallback2.F90 to deal with REAL(16) (and
maybe REAL17).

> - If I get this right, to take one example, the Fortran front-end
> will emit a call to gfortran_acospi_r4(), libgfortran provides this
> as a wrapper calling acospif(), which is called either from libm
> or from libgfortran.

Yes, that is correct.  I tried to install __builtin_acospif in
trans-intrinsic to generate a direct call to acospif, but that
led to many hours/days of frustration.  I gave up.

> This is different from other math library functions, like ACOS()
> where the acosf() call is generated directly from the front-end
> (and then the implementation comes either from libm or from
> libgfortran). Why not follow our usual way of doing things?

I gave up on that approach when I got some real obscure error
about some math function which I did not touch. 


> - On most targets, cospi() and friends are not available. Therefore,
> we end up doing the fallback (with limited precision as you noted)
> but with a lot of indirection. We could generate that code directly
> in the front-end, couldn’t we?

The precision of the fallback routines isn't too bad.  More later
today.  Late for a doctor's approintment.

-- 
Steve


Re: [Fortran] half-cycle trig functions and atan[d] fixes

2024-01-23 Thread Janne Blomqvist
On Tue, Jan 23, 2024 at 11:09 AM FX Coudert  wrote:
>
> Hi Steve,

Hello, long time no see.

> Thanks for the patch. I’ll take time to do a proper review, but after a first 
> read I had the following questions:
>
> - "an OS's libm may/will contain cospi(), etc.”: do those functions conform 
> to any standard? Are there plans to implement them outside FreeBSD at this 
> point?

acospi, asinpi, atan2pi, atanpi, cospi, sinpi, tanpi are all in IEEE
754-2019, and are slated to be part of C23. I would assume actively
developed libm's will eventually catch up and implement them.

> - If I get this right, to take one example, the Fortran front-end will emit a 
> call to gfortran_acospi_r4(), libgfortran provides this as a wrapper calling 
> acospif(), which is called either from libm or from libgfortran. This is 
> different from other math library functions, like ACOS() where the acosf() 
> call is generated directly from the front-end (and then the implementation 
> comes either from libm or from libgfortran). Why not follow our usual way of 
> doing things?

Good point, that's what we've done in c99_functions.c in libgfortran.
We should probably do this so we can skip jumping via libgfortran when
libm implements these directly.

> - On most targets, cospi() and friends are not available. Therefore, we end 
> up doing the fallback (with limited precision as you noted) but with a lot of 
> indirection. We could generate that code directly in the front-end, couldn’t 
> we?

The frontend generally doesn't know what the target libm implements,
hence it's better to just generate the call, and if necessary have a
barebones implementation in libgfortran if libm doesn't implement it
properly.


-- 
Janne Blomqvist


Re: [Fortran] half-cycle trig functions and atan[d] fixes

2024-01-23 Thread FX Coudert
Hi Steve,

Thanks for the patch. I’ll take time to do a proper review, but after a first 
read I had the following questions:

- "an OS's libm may/will contain cospi(), etc.”: do those functions conform to 
any standard? Are there plans to implement them outside FreeBSD at this point?

- On systems where libquadmath is used for _Float128, does libquadmath contain 
the implementation of the q() variants for those functions?

- If I get this right, to take one example, the Fortran front-end will emit a 
call to gfortran_acospi_r4(), libgfortran provides this as a wrapper calling 
acospif(), which is called either from libm or from libgfortran. This is 
different from other math library functions, like ACOS() where the acosf() call 
is generated directly from the front-end (and then the implementation comes 
either from libm or from libgfortran). Why not follow our usual way of doing 
things?

- On most targets, cospi() and friends are not available. Therefore, we end up 
doing the fallback (with limited precision as you noted) but with a lot of 
indirection. We could generate that code directly in the front-end, couldn’t we?


Best,
FX