Path: blob/main/contrib/arm-optimized-routines/math/test/ulp.c
48254 views
/*1* ULP error checking tool for math functions.2*3* Copyright (c) 2019-2024, Arm Limited.4* SPDX-License-Identifier: MIT OR Apache-2.0 WITH LLVM-exception5*/67#if WANT_SVE_TESTS8# if __aarch64__ && __linux__9# ifdef __clang__10# pragma clang attribute push(__attribute__((target("sve"))), \11apply_to = any(function))12# else13# pragma GCC target("+sve")14# endif15# else16# error "SVE not supported - please disable WANT_SVE_TESTS"17# endif18#endif1920#define _GNU_SOURCE21#include <ctype.h>22#include <fenv.h>23#include <float.h>24#include <math.h>25#include <stdint.h>26#include <stdio.h>27#include <stdlib.h>28#include <string.h>29#include "mathlib.h"3031#include "trigpi_references.h"3233/* Don't depend on mpfr by default. */34#ifndef USE_MPFR35# define USE_MPFR 036#endif37#if USE_MPFR38# include <mpfr.h>39#endif4041static uint64_t seed = 0x0123456789abcdef;42static uint64_t43rand64 (void)44{45seed = 6364136223846793005ull * seed + 1;46return seed ^ (seed >> 32);47}4849/* Uniform random in [0,n]. */50static uint64_t51randn (uint64_t n)52{53uint64_t r, m;5455if (n == 0)56return 0;57n++;58if (n == 0)59return rand64 ();60for (;;)61{62r = rand64 ();63m = r % n;64if (r - m <= -n)65return m;66}67}6869struct gen70{71uint64_t start;72uint64_t len;73uint64_t start2;74uint64_t len2;75uint64_t off;76uint64_t step;77uint64_t cnt;78};7980struct args_f181{82float x;83};8485struct args_f286{87float x;88float x2;89};9091struct args_d192{93double x;94};9596struct args_d297{98double x;99double x2;100};101102/* result = y + tail*2^ulpexp. */103struct ret_f104{105float y;106double tail;107int ulpexp;108int ex;109int ex_may;110};111112struct ret_d113{114double y;115double tail;116int ulpexp;117int ex;118int ex_may;119};120121static inline uint64_t122next1 (struct gen *g)123{124/* For single argument use randomized incremental steps,125that produce dense sampling without collisions and allow126testing all inputs in a range. */127uint64_t r = g->start + g->off;128g->off += g->step + randn (g->step / 2);129if (g->off > g->len)130g->off -= g->len; /* hack. */131return r;132}133134static inline uint64_t135next2 (uint64_t *x2, struct gen *g)136{137/* For two arguments use uniform random sampling. */138uint64_t r = g->start + randn (g->len);139*x2 = g->start2 + randn (g->len2);140return r;141}142143static struct args_f1144next_f1 (void *g)145{146return (struct args_f1){asfloat (next1 (g))};147}148149static struct args_f2150next_f2 (void *g)151{152uint64_t x2;153uint64_t x = next2 (&x2, g);154return (struct args_f2){asfloat (x), asfloat (x2)};155}156157static struct args_d1158next_d1 (void *g)159{160return (struct args_d1){asdouble (next1 (g))};161}162163static struct args_d2164next_d2 (void *g)165{166uint64_t x2;167uint64_t x = next2 (&x2, g);168return (struct args_d2){asdouble (x), asdouble (x2)};169}170171/* A bit of a hack: call vector functions twice with the same172input in lane 0 but a different value in other lanes: once173with an in-range value and then with a special case value. */174static int secondcall;175176/* Wrappers for vector functions. */177#if __aarch64__ && __linux__178/* First element of fv and dv may be changed by -c argument. */179static float fv[2] = {1.0f, -INFINITY};180static double dv[2] = {1.0, -INFINITY};181static inline float32x4_t182argf (float x)183{184return (float32x4_t){ x, x, x, fv[secondcall] };185}186static inline float64x2_t187argd (double x)188{189return (float64x2_t){ x, dv[secondcall] };190}191#if WANT_SVE_TESTS192#include <arm_sve.h>193194static inline svfloat32_t195svargf (float x)196{197int n = svcntw ();198float base[n];199for (int i = 0; i < n; i++)200base[i] = (float) x;201base[n - 1] = (float) fv[secondcall];202return svld1 (svptrue_b32 (), base);203}204static inline svfloat64_t205svargd (double x)206{207int n = svcntd ();208double base[n];209for (int i = 0; i < n; i++)210base[i] = x;211base[n - 1] = dv[secondcall];212return svld1 (svptrue_b64 (), base);213}214static inline float215svretf (svfloat32_t vec, svbool_t pg)216{217return svlastb_f32 (svpfirst (pg, svpfalse ()), vec);218}219static inline double220svretd (svfloat64_t vec, svbool_t pg)221{222return svlastb_f64 (svpfirst (pg, svpfalse ()), vec);223}224225static inline svbool_t226parse_pg (uint64_t p, int is_single)227{228if (is_single)229{230uint32_t tmp[svcntw ()];231for (unsigned i = 0; i < svcntw (); i++)232tmp[i] = (p >> i) & 1;233return svcmpne (svptrue_b32 (), svld1 (svptrue_b32 (), tmp), 0);234}235else236{237uint64_t tmp[svcntd ()];238for (unsigned i = 0; i < svcntd (); i++)239tmp[i] = (p >> i) & 1;240return svcmpne (svptrue_b64 (), svld1 (svptrue_b64 (), tmp), 0);241}242}243# endif244#endif245246struct conf247{248int r;249int rc;250int quiet;251int mpfr;252int fenv;253unsigned long long n;254double softlim;255double errlim;256int ignore_zero_sign;257#if WANT_SVE_TESTS258svbool_t *pg;259#endif260};261262#include "test/ulp_wrappers.h"263264struct fun265{266const char *name;267int arity;268int singleprec;269int twice;270int is_predicated;271union272{273float (*f1) (float);274float (*f2) (float, float);275double (*d1) (double);276double (*d2) (double, double);277#if WANT_SVE_TESTS278float (*f1_pred) (svbool_t, float);279float (*f2_pred) (svbool_t, float, float);280double (*d1_pred) (svbool_t, double);281double (*d2_pred) (svbool_t, double, double);282#endif283} fun;284union285{286double (*f1) (double);287double (*f2) (double, double);288long double (*d1) (long double);289long double (*d2) (long double, long double);290} fun_long;291#if USE_MPFR292union293{294int (*f1) (mpfr_t, const mpfr_t, mpfr_rnd_t);295int (*f2) (mpfr_t, const mpfr_t, const mpfr_t, mpfr_rnd_t);296int (*d1) (mpfr_t, const mpfr_t, mpfr_rnd_t);297int (*d2) (mpfr_t, const mpfr_t, const mpfr_t, mpfr_rnd_t);298} fun_mpfr;299#endif300};301302// clang-format off303static const struct fun fun[] = {304#if USE_MPFR305# define F(x, x_wrap, x_long, x_mpfr, a, s, t, twice) \306{ #x, a, s, twice, 0, { .t = x_wrap }, { .t = x_long }, { .t = x_mpfr } },307# define SVF(x, x_wrap, x_long, x_mpfr, a, s, t, twice) \308{ #x, a, s, twice, 1, { .t##_pred = x_wrap }, { .t = x_long }, { .t = x_mpfr } },309#else310# define F(x, x_wrap, x_long, x_mpfr, a, s, t, twice) \311{ #x, a, s, twice, 0, { .t = x_wrap }, { .t = x_long } },312# define SVF(x, x_wrap, x_long, x_mpfr, a, s, t, twice) \313{ #x, a, s, twice, 1, { .t##_pred = x_wrap }, { .t = x_long } },314#endif315#define F1(x) F (x##f, x##f, x, mpfr_##x, 1, 1, f1, 0)316#define F2(x) F (x##f, x##f, x, mpfr_##x, 2, 1, f2, 0)317#define D1(x) F (x, x, x##l, mpfr_##x, 1, 0, d1, 0)318#define D2(x) F (x, x, x##l, mpfr_##x, 2, 0, d2, 0)319/* Neon routines. */320#define ZVNF1(x) F (_ZGVnN4v_##x##f, Z_##x##f, x, mpfr_##x, 1, 1, f1, 0)321#define ZVNF2(x) F (_ZGVnN4vv_##x##f, Z_##x##f, x, mpfr_##x, 2, 1, f2, 0)322#define ZVND1(x) F (_ZGVnN2v_##x, Z_##x, x##l, mpfr_##x, 1, 0, d1, 0)323#define ZVND2(x) F (_ZGVnN2vv_##x, Z_##x, x##l, mpfr_##x, 2, 0, d2, 0)324/* SVE routines. */325#define ZSVF1(x) SVF (_ZGVsMxv_##x##f, Z_sv_##x##f, x, mpfr_##x, 1, 1, f1, 0)326#define ZSVF2(x) SVF (_ZGVsMxvv_##x##f, Z_sv_##x##f, x, mpfr_##x, 2, 1, f2, 0)327#define ZSVD1(x) SVF (_ZGVsMxv_##x, Z_sv_##x, x##l, mpfr_##x, 1, 0, d1, 0)328#define ZSVD2(x) SVF (_ZGVsMxvv_##x, Z_sv_##x, x##l, mpfr_##x, 2, 0, d2, 0)329330#include "test/ulp_funcs.h"331332#undef F333#undef F1334#undef F2335#undef D1336#undef D2337#undef ZSVF1338#undef ZSVF2339#undef ZSVD1340#undef ZSVD2341{ 0 }342};343// clang-format on344345/* Boilerplate for generic calls. */346347static inline int348ulpscale_f (float x)349{350int e = asuint (x) >> 23 & 0xff;351if (!e)352e++;353return e - 0x7f - 23;354}355static inline int356ulpscale_d (double x)357{358int e = asuint64 (x) >> 52 & 0x7ff;359if (!e)360e++;361return e - 0x3ff - 52;362}363static inline float364call_f1 (const struct fun *f, struct args_f1 a, const struct conf *conf)365{366#if WANT_SVE_TESTS367if (f->is_predicated)368return f->fun.f1_pred (*conf->pg, a.x);369#endif370return f->fun.f1 (a.x);371}372static inline float373call_f2 (const struct fun *f, struct args_f2 a, const struct conf *conf)374{375#if WANT_SVE_TESTS376if (f->is_predicated)377return f->fun.f2_pred (*conf->pg, a.x, a.x2);378#endif379return f->fun.f2 (a.x, a.x2);380}381382static inline double383call_d1 (const struct fun *f, struct args_d1 a, const struct conf *conf)384{385#if WANT_SVE_TESTS386if (f->is_predicated)387return f->fun.d1_pred (*conf->pg, a.x);388#endif389return f->fun.d1 (a.x);390}391static inline double392call_d2 (const struct fun *f, struct args_d2 a, const struct conf *conf)393{394#if WANT_SVE_TESTS395if (f->is_predicated)396return f->fun.d2_pred (*conf->pg, a.x, a.x2);397#endif398return f->fun.d2 (a.x, a.x2);399}400static inline double401call_long_f1 (const struct fun *f, struct args_f1 a)402{403return f->fun_long.f1 (a.x);404}405static inline double406call_long_f2 (const struct fun *f, struct args_f2 a)407{408return f->fun_long.f2 (a.x, a.x2);409}410static inline long double411call_long_d1 (const struct fun *f, struct args_d1 a)412{413return f->fun_long.d1 (a.x);414}415static inline long double416call_long_d2 (const struct fun *f, struct args_d2 a)417{418return f->fun_long.d2 (a.x, a.x2);419}420static inline void421printcall_f1 (const struct fun *f, struct args_f1 a)422{423printf ("%s(%a)", f->name, a.x);424}425static inline void426printcall_f2 (const struct fun *f, struct args_f2 a)427{428printf ("%s(%a, %a)", f->name, a.x, a.x2);429}430static inline void431printcall_d1 (const struct fun *f, struct args_d1 a)432{433printf ("%s(%a)", f->name, a.x);434}435static inline void436printcall_d2 (const struct fun *f, struct args_d2 a)437{438printf ("%s(%a, %a)", f->name, a.x, a.x2);439}440static inline void441printgen_f1 (const struct fun *f, struct gen *gen)442{443printf ("%s in [%a;%a]", f->name, asfloat (gen->start),444asfloat (gen->start + gen->len));445}446static inline void447printgen_f2 (const struct fun *f, struct gen *gen)448{449printf ("%s in [%a;%a] x [%a;%a]", f->name, asfloat (gen->start),450asfloat (gen->start + gen->len), asfloat (gen->start2),451asfloat (gen->start2 + gen->len2));452}453static inline void454printgen_d1 (const struct fun *f, struct gen *gen)455{456printf ("%s in [%a;%a]", f->name, asdouble (gen->start),457asdouble (gen->start + gen->len));458}459static inline void460printgen_d2 (const struct fun *f, struct gen *gen)461{462printf ("%s in [%a;%a] x [%a;%a]", f->name, asdouble (gen->start),463asdouble (gen->start + gen->len), asdouble (gen->start2),464asdouble (gen->start2 + gen->len2));465}466467#define reduce_f1(a, f, op) (f (a.x))468#define reduce_f2(a, f, op) (f (a.x) op f (a.x2))469#define reduce_d1(a, f, op) (f (a.x))470#define reduce_d2(a, f, op) (f (a.x) op f (a.x2))471472#ifndef IEEE_754_2008_SNAN473# define IEEE_754_2008_SNAN 1474#endif475static inline int476issignaling_f (float x)477{478uint32_t ix = asuint (x);479if (!IEEE_754_2008_SNAN)480return (ix & 0x7fc00000) == 0x7fc00000;481return 2 * (ix ^ 0x00400000) > 2u * 0x7fc00000;482}483static inline int484issignaling_d (double x)485{486uint64_t ix = asuint64 (x);487if (!IEEE_754_2008_SNAN)488return (ix & 0x7ff8000000000000) == 0x7ff8000000000000;489return 2 * (ix ^ 0x0008000000000000) > 2 * 0x7ff8000000000000ULL;490}491492#if USE_MPFR493static mpfr_rnd_t494rmap (int r)495{496switch (r)497{498case FE_TONEAREST:499return MPFR_RNDN;500case FE_TOWARDZERO:501return MPFR_RNDZ;502case FE_UPWARD:503return MPFR_RNDU;504case FE_DOWNWARD:505return MPFR_RNDD;506}507return -1;508}509510#define prec_mpfr_f 50511#define prec_mpfr_d 80512#define prec_f 24513#define prec_d 53514#define emin_f -148515#define emin_d -1073516#define emax_f 128517#define emax_d 1024518static inline int519call_mpfr_f1 (mpfr_t y, const struct fun *f, struct args_f1 a, mpfr_rnd_t r)520{521MPFR_DECL_INIT (x, prec_f);522mpfr_set_flt (x, a.x, MPFR_RNDN);523return f->fun_mpfr.f1 (y, x, r);524}525static inline int526call_mpfr_f2 (mpfr_t y, const struct fun *f, struct args_f2 a, mpfr_rnd_t r)527{528MPFR_DECL_INIT (x, prec_f);529MPFR_DECL_INIT (x2, prec_f);530mpfr_set_flt (x, a.x, MPFR_RNDN);531mpfr_set_flt (x2, a.x2, MPFR_RNDN);532return f->fun_mpfr.f2 (y, x, x2, r);533}534static inline int535call_mpfr_d1 (mpfr_t y, const struct fun *f, struct args_d1 a, mpfr_rnd_t r)536{537MPFR_DECL_INIT (x, prec_d);538mpfr_set_d (x, a.x, MPFR_RNDN);539return f->fun_mpfr.d1 (y, x, r);540}541static inline int542call_mpfr_d2 (mpfr_t y, const struct fun *f, struct args_d2 a, mpfr_rnd_t r)543{544MPFR_DECL_INIT (x, prec_d);545MPFR_DECL_INIT (x2, prec_d);546mpfr_set_d (x, a.x, MPFR_RNDN);547mpfr_set_d (x2, a.x2, MPFR_RNDN);548return f->fun_mpfr.d2 (y, x, x2, r);549}550#endif551552#define float_f float553#define double_f double554#define copysign_f copysignf555#define nextafter_f nextafterf556#define fabs_f fabsf557#define asuint_f asuint558#define asfloat_f asfloat559#define scalbn_f scalbnf560#define lscalbn_f scalbn561#define halfinf_f 0x1p127f562#define min_normal_f 0x1p-126f563564#define float_d double565#define double_d long double566#define copysign_d copysign567#define nextafter_d nextafter568#define fabs_d fabs569#define asuint_d asuint64570#define asfloat_d asdouble571#define scalbn_d scalbn572#define lscalbn_d scalbnl573#define halfinf_d 0x1p1023574#define min_normal_d 0x1p-1022575576#define NEW_RT577#define RT(x) x##_f578#define T(x) x##_f1579#include "ulp.h"580#undef T581#define T(x) x##_f2582#include "ulp.h"583#undef T584#undef RT585586#define NEW_RT587#define RT(x) x##_d588#define T(x) x##_d1589#include "ulp.h"590#undef T591#define T(x) x##_d2592#include "ulp.h"593#undef T594#undef RT595596static void597usage (void)598{599puts ("./ulp [-q] [-m] [-f] [-r {n|u|d|z}] [-l soft-ulplimit] [-e ulplimit] func "600"lo [hi [x lo2 hi2] [count]]");601puts ("Compares func against a higher precision implementation in [lo; hi].");602puts ("-q: quiet.");603puts ("-m: use mpfr even if faster method is available.");604puts ("-f: disable fenv exceptions testing.");605#ifdef ___vpcs606puts ("-c: neutral 'control value' to test behaviour when one lane can affect another. \n"607" This should be different from tested input in other lanes, and non-special \n"608" (i.e. should not trigger fenv exceptions). Default is 1.");609#endif610#if WANT_SVE_TESTS611puts ("-p: integer input for controlling predicate passed to SVE function. "612"If bit N is set, lane N is activated (bits past the vector length "613"are ignored). Default is UINT64_MAX (ptrue).");614#endif615puts ("-z: ignore sign of 0.");616puts ("Supported func:");617for (const struct fun *f = fun; f->name; f++)618printf ("\t%s\n", f->name);619exit (1);620}621622static int623cmp (const struct fun *f, struct gen *gen, const struct conf *conf)624{625int r = 1;626if (f->arity == 1 && f->singleprec)627r = cmp_f1 (f, gen, conf);628else if (f->arity == 2 && f->singleprec)629r = cmp_f2 (f, gen, conf);630else if (f->arity == 1 && !f->singleprec)631r = cmp_d1 (f, gen, conf);632else if (f->arity == 2 && !f->singleprec)633r = cmp_d2 (f, gen, conf);634else635usage ();636return r;637}638639static uint64_t640getnum (const char *s, int singleprec)641{642// int i;643uint64_t sign = 0;644// char buf[12];645646if (s[0] == '+')647s++;648else if (s[0] == '-')649{650sign = singleprec ? 1ULL << 31 : 1ULL << 63;651s++;652}653654/* Sentinel value for failed parse. */655char *should_not_be_s = NULL;656657/* 0xXXXX is treated as bit representation, '-' flips the sign bit. */658if (s[0] == '0' && tolower (s[1]) == 'x' && strchr (s, 'p') == 0)659{660uint64_t out = sign ^ strtoull (s, &should_not_be_s, 0);661if (should_not_be_s == s)662{663printf ("ERROR: Could not parse '%s'\n", s);664exit (1);665}666return out;667}668// /* SNaN, QNaN, NaN, Inf. */669// for (i=0; s[i] && i < sizeof buf; i++)670// buf[i] = tolower(s[i]);671// buf[i] = 0;672// if (strcmp(buf, "snan") == 0)673// return sign | (singleprec ? 0x7fa00000 : 0x7ff4000000000000);674// if (strcmp(buf, "qnan") == 0 || strcmp(buf, "nan") == 0)675// return sign | (singleprec ? 0x7fc00000 : 0x7ff8000000000000);676// if (strcmp(buf, "inf") == 0 || strcmp(buf, "infinity") == 0)677// return sign | (singleprec ? 0x7f800000 : 0x7ff0000000000000);678/* Otherwise assume it's a floating-point literal. */679uint64_t out = sign680| (singleprec ? asuint (strtof (s, &should_not_be_s))681: asuint64 (strtod (s, &should_not_be_s)));682if (should_not_be_s == s)683{684printf ("ERROR: Could not parse '%s'\n", s);685exit (1);686}687688return out;689}690691static void692parsegen (struct gen *g, int argc, char *argv[], const struct fun *f)693{694int singleprec = f->singleprec;695int arity = f->arity;696uint64_t a, b, a2, b2, n;697if (argc < 1)698usage ();699b = a = getnum (argv[0], singleprec);700n = 0;701if (argc > 1 && strcmp (argv[1], "x") == 0)702{703argc -= 2;704argv += 2;705}706else if (argc > 1)707{708b = getnum (argv[1], singleprec);709if (argc > 2 && strcmp (argv[2], "x") == 0)710{711argc -= 3;712argv += 3;713}714}715b2 = a2 = getnum (argv[0], singleprec);716if (argc > 1)717b2 = getnum (argv[1], singleprec);718if (argc > 2)719n = strtoull (argv[2], 0, 0);720if (argc > 3)721usage ();722//printf("ab %lx %lx ab2 %lx %lx n %lu\n", a, b, a2, b2, n);723if (arity == 1)724{725g->start = a;726g->len = b - a;727if (n - 1 > b - a)728n = b - a + 1;729g->off = 0;730g->step = n ? (g->len + 1) / n : 1;731g->start2 = g->len2 = 0;732g->cnt = n;733}734else if (arity == 2)735{736g->start = a;737g->len = b - a;738g->off = g->step = 0;739g->start2 = a2;740g->len2 = b2 - a2;741g->cnt = n;742}743else744usage ();745}746747int748main (int argc, char *argv[])749{750const struct fun *f;751struct gen gen;752struct conf conf;753conf.rc = 'n';754conf.quiet = 0;755conf.mpfr = 0;756conf.fenv = 1;757conf.softlim = 0;758conf.errlim = INFINITY;759conf.ignore_zero_sign = 0;760#if WANT_SVE_TESTS761uint64_t pg_int = UINT64_MAX;762#endif763for (;;)764{765argc--;766argv++;767if (argc < 1)768usage ();769if (argv[0][0] != '-')770break;771switch (argv[0][1])772{773case 'e':774argc--;775argv++;776if (argc < 1)777usage ();778conf.errlim = strtod (argv[0], 0);779break;780case 'f':781conf.fenv = 0;782break;783case 'l':784argc--;785argv++;786if (argc < 1)787usage ();788conf.softlim = strtod (argv[0], 0);789break;790case 'm':791conf.mpfr = 1;792break;793case 'q':794conf.quiet = 1;795break;796case 'r':797conf.rc = argv[0][2];798if (!conf.rc)799{800argc--;801argv++;802if (argc < 1 || argv[0][1] != '\0')803usage ();804conf.rc = argv[0][0];805}806break;807case 'z':808conf.ignore_zero_sign = 1;809break;810#if __aarch64__ && __linux__811case 'c':812argc--;813argv++;814fv[0] = strtof(argv[0], 0);815dv[0] = strtod(argv[0], 0);816break;817#endif818#if WANT_SVE_TESTS819case 'p':820argc--;821argv++;822pg_int = strtoull (argv[0], 0, 0);823break;824#endif825default:826usage ();827}828}829switch (conf.rc)830{831case 'n':832conf.r = FE_TONEAREST;833break;834case 'u':835conf.r = FE_UPWARD;836break;837case 'd':838conf.r = FE_DOWNWARD;839break;840case 'z':841conf.r = FE_TOWARDZERO;842break;843default:844usage ();845}846for (f = fun; f->name; f++)847if (strcmp (argv[0], f->name) == 0)848break;849if (!f->name)850{851#ifndef __vpcs852/* Ignore vector math functions if vector math is not supported. */853if (strncmp (argv[0], "_ZGVnN", 6) == 0)854exit (0);855#endif856#if !WANT_SVE_TESTS857if (strncmp (argv[0], "_ZGVsMxv", 8) == 0)858exit (0);859#endif860printf ("math function %s not supported\n", argv[0]);861exit (1);862}863if (!f->singleprec && LDBL_MANT_DIG == DBL_MANT_DIG)864conf.mpfr = 1; /* Use mpfr if long double has no extra precision. */865if (!USE_MPFR && conf.mpfr)866{867puts ("mpfr is not available.");868return 0;869}870argc--;871argv++;872parsegen (&gen, argc, argv, f);873conf.n = gen.cnt;874#if WANT_SVE_TESTS875svbool_t pg = parse_pg (pg_int, f->singleprec);876conf.pg = &pg;877#endif878return cmp (f, &gen, &conf);879}880881#if __aarch64__ && __linux__ && WANT_SVE_TESTS && defined(__clang__)882# pragma clang attribute pop883#endif884885886