Skip to content

Commit

Permalink
Add a probe for pow() corner cases with Inf and NaN.
Browse files Browse the repository at this point in the history
Solaris doesn't default to being POSIX conformat, but NQP and the spectests
assume that we are. It's not clear *how* to flip the defaults with gcc, so
code around the problem.

Disable JITting `pow_n` if the C library pow() isn't conformant.
(I don't have x86 Solaris to test, but given how thorough Sun's engineering
was, I assume that it's C library behaves identically to Sparc's, so likely
it would JIT to the LTA implementation.)
  • Loading branch information
nwc10 committed Dec 28, 2020
1 parent f0b09b3 commit d61882f
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 2 deletions.
2 changes: 2 additions & 0 deletions Configure.pl
Expand Up @@ -491,13 +491,15 @@ sub uniq {
build::auto::detect_cross(\%config, \%defaults);
build::probe::static_inline_cross(\%config, \%defaults);
build::probe::thread_local_cross(\%config, \%defaults);
build::probe::substandard_pow_cross(\%config, \%defaults);
build::probe::unaligned_access_cross(\%config, \%defaults);
build::probe::ptr_size_cross(\%config, \%defaults);
}
else {
build::auto::detect_native(\%config, \%defaults);
build::probe::static_inline_native(\%config, \%defaults);
build::probe::thread_local_native(\%config, \%defaults);
build::probe::substandard_pow(\%config, \%defaults);
build::probe::unaligned_access(\%config, \%defaults);
build::probe::ptr_size_native(\%config, \%defaults);
}
Expand Down
3 changes: 3 additions & 0 deletions build/config.h.in
Expand Up @@ -65,6 +65,9 @@

#define MVM_PTR_SIZE @ptr_size@

#if @has_substandard_pow@
#define MVM_HAS_SUBSTANDARD_POW
#endif

#if @havebooltype@
#define MVM_BOOL @booltype@
Expand Down
116 changes: 116 additions & 0 deletions build/probe.pm
Expand Up @@ -353,6 +353,122 @@ EOT
}
}

sub substandard_pow_cross {
my ($config) = @_;
$config->{has_substandard_pow} = 0;
}

sub substandard_pow {
my ($config) = @_;
my $restore = _to_probe_dir();
print ::dots(' probing if your pow() handles NaN and Inf corner cases');
my $file = 'try.c';
_spew($file, <<'EOT');
#include <math.h>
#include <stdio.h>
int main(int argc, char **argv) {
/* Hopefully these games defeat the optimiser, such that we call the runtime
* library pow, instead of having the C compiler optimiser constant fold it.
* Without this (for me on Solaris with gcc)
* pow(1.0, NaN) will be constant folded always
* pow(1.0, Inf) will be constant folded if optimisation is enabled
*/
double one = argv[0][0] != '\0';
double zero = one - 1.0;
double nan = sqrt(-1);
if (nan == nan) {
#ifdef CHATTY
fprintf(stderr, "Can't generate NaN - get %g\n", nan);
#endif
return 1;
}
double inf = pow(10.0, pow(10.0, 100));
if (inf != 2.0 * inf) {
#ifdef CHATTY
fprintf(stderr, "Can't generate Inf - get %g\n", inf);
#endif
return 2;
}
double neg_inf = -inf;
if (! (neg_inf < 0.0)) {
#ifdef CHATTY
fprintf(stderr, "Can't generate -Inf - get %g\n", inf);
#endif
return 3;
}
if (neg_inf != 2.0 * neg_inf) {
#ifdef CHATTY
fprintf(stderr, "Can't generate -Inf - get %g\n", inf);
#endif
return 4;
}
/* Solaris is documented not to be conformant with SUSv3 (which I think
* is POSIX 2001) unless
* "the application was compiled with the c99 compiler driver"
* which as best I can tell gcc doesn't, even with -std=c99 */
double got = pow(one, nan);
if (got != one) {
#ifdef CHATTY
fprintf(stderr, "1.0, NaN: pow(%g, %g) is %g, not 1.0\n", one, nan, got);
#endif
return 5;
}
got = pow(one, inf);
if (got != one) {
#ifdef CHATTY
fprintf(stderr, "1.0, Inf: pow(%g, %g) is %g, not 1.0\n", one, inf, got);
#endif
return 6;
}
got = pow(one, neg_inf);
if (got != one) {
#ifdef CHATTY
fprintf(stderr, "1.0, -Inf: pow(%g, %g) is %g, not 1.0\n", one, neg_inf, got);
#endif
return 7;
}
/* However, Solaris does state that unconditionally:
* For any value of x (including NaN), if y is +0, 1.0 is returned.
* (without repeating that caveat about the c99 compiler driver)
* and yet behaviour of gcc and *Solaris* studio is consistent in returning
* NaN for pow(NaN, 0.0). Oracle studio seems to default to c99.
*/
got = pow(nan, zero);
if (got != one) {
#ifdef CHATTY
fprintf(stderr, "NaN, 0.0: pow(%g, %g) is %g, not 1.0\n", nan, zero, got);
#endif
return 8;
}
/* Not seen either of these fail anywhere, but let's test anyway: */
got = pow(inf, zero);
if (got != one) {
#ifdef CHATTY
fprintf(stderr, "Inf, 0.0: pow(%g, %g) is %g, not 1.0\n", inf, zero, got);
#endif
return 9;
}
got = pow(neg_inf, zero);
if (got != one) {
#ifdef CHATTY
fprintf(stderr, "-Inf, 0.0: pow(%g, %g) is %g, not 1.0\n", neg_inf, zero, got);
#endif
return 10;
}
return 0;
}
EOT

my $pow_good = compile($config, 'try') && system('./try') == 0;
print $pow_good ? "YES\n": "NO\n";
$config->{has_substandard_pow} = !$pow_good;
}


sub specific_werror {
my ($config) = @_;
my $restore = _to_probe_dir();
Expand Down
11 changes: 9 additions & 2 deletions src/core/interp.c
Expand Up @@ -800,10 +800,17 @@ void MVM_interp_run(MVMThreadContext *tc, void (*initial_invoke)(MVMThreadContex
GET_REG(cur_op, 0).n64 = fabs(GET_REG(cur_op, 2).n64);
cur_op += 4;
goto NEXT;
OP(pow_n):
GET_REG(cur_op, 0).n64 = pow(GET_REG(cur_op, 2).n64, GET_REG(cur_op, 4).n64);
OP(pow_n): {
MVMnum64 x = GET_REG(cur_op, 2).n64;
MVMnum64 y = GET_REG(cur_op, 4).n64;
GET_REG(cur_op, 0).n64 =
#ifdef MVM_HAS_SUBSTANDARD_POW
y == 0.0 || x == 1.0 ? 1.0 :
#endif
pow(x, y);
cur_op += 6;
goto NEXT;
}
OP(ceil_n):
GET_REG(cur_op, 0).n64 = ceil(GET_REG(cur_op, 2).n64);
cur_op += 4;
Expand Down
4 changes: 4 additions & 0 deletions src/jit/graph.c
Expand Up @@ -336,7 +336,9 @@ static void * op_to_func(MVMThreadContext *tc, MVMint16 opcode) {
case MVM_OP_pow_I: return MVM_bigint_pow;
case MVM_OP_rand_I: return MVM_bigint_rand;
case MVM_OP_abs_n: return fabs;
#ifndef MVM_HAS_SUBSTANDARD_POW
case MVM_OP_pow_n: return pow;
#endif
case MVM_OP_time_n: return MVM_proc_time_n;
case MVM_OP_randscale_n: return MVM_proc_randscale_n;
case MVM_OP_isnanorinf: return MVM_num_isnanorinf;
Expand Down Expand Up @@ -3511,7 +3513,9 @@ static MVMint32 consume_ins(MVMThreadContext *tc, MVMJitGraph *jg,
MVM_JIT_RV_NUM, dst);
break;
}
#ifndef MVM_HAS_SUBSTANDARD_POW
case MVM_OP_pow_n:
#endif
case MVM_OP_atan2_n: {
MVMint16 dst = ins->operands[0].reg.orig;
MVMint16 a = ins->operands[1].reg.orig;
Expand Down

0 comments on commit d61882f

Please sign in to comment.