cosmopolitan/third_party/quickjs/eq.c
Justine Tunney fa20edc44d
Reduce header complexity
- Remove most __ASSEMBLER__ __LINKER__ ifdefs
- Rename libc/intrin/bits.h to libc/serialize.h
- Block pthread cancelation in fchmodat() polyfill
- Remove `clang-format off` statements in third_party
2023-11-28 14:39:42 -08:00

458 lines
15 KiB
C

/*
* QuickJS Javascript Engine
*
* Copyright (c) 2017-2021 Fabrice Bellard
* Copyright (c) 2017-2021 Charlie Gordon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "third_party/quickjs/internal.h"
#include "third_party/quickjs/libregexp.h"
#include "third_party/quickjs/quickjs.h"
asm(".ident\t\"\\n\\n\
QuickJS (MIT License)\\n\
Copyright (c) 2017-2021 Fabrice Bellard\\n\
Copyright (c) 2017-2021 Charlie Gordon\"");
asm(".include \"libc/disclaimer.inc\"");
/* XXX: Should take JSValueConst arguments */
BOOL js_strict_eq2(JSContext *ctx, JSValue op1, JSValue op2,
JSStrictEqModeEnum eq_mode)
{
BOOL res;
int tag1, tag2;
double d1, d2;
tag1 = JS_VALUE_GET_NORM_TAG(op1);
tag2 = JS_VALUE_GET_NORM_TAG(op2);
switch(tag1) {
case JS_TAG_BOOL:
if (tag1 != tag2) {
res = FALSE;
} else {
res = JS_VALUE_GET_INT(op1) == JS_VALUE_GET_INT(op2);
goto done_no_free;
}
break;
case JS_TAG_NULL:
case JS_TAG_UNDEFINED:
res = (tag1 == tag2);
break;
case JS_TAG_STRING:
{
JSString *p1, *p2;
if (tag1 != tag2) {
res = FALSE;
} else {
p1 = JS_VALUE_GET_STRING(op1);
p2 = JS_VALUE_GET_STRING(op2);
res = (js_string_compare(ctx, p1, p2) == 0);
}
}
break;
case JS_TAG_SYMBOL:
{
JSAtomStruct *p1, *p2;
if (tag1 != tag2) {
res = FALSE;
} else {
p1 = JS_VALUE_GET_PTR(op1);
p2 = JS_VALUE_GET_PTR(op2);
res = (p1 == p2);
}
}
break;
case JS_TAG_OBJECT:
if (tag1 != tag2)
res = FALSE;
else
res = JS_VALUE_GET_OBJ(op1) == JS_VALUE_GET_OBJ(op2);
break;
case JS_TAG_INT:
d1 = JS_VALUE_GET_INT(op1);
if (tag2 == JS_TAG_INT) {
d2 = JS_VALUE_GET_INT(op2);
goto number_test;
} else if (tag2 == JS_TAG_FLOAT64) {
d2 = JS_VALUE_GET_FLOAT64(op2);
goto number_test;
} else {
res = FALSE;
}
break;
case JS_TAG_FLOAT64:
d1 = JS_VALUE_GET_FLOAT64(op1);
if (tag2 == JS_TAG_FLOAT64) {
d2 = JS_VALUE_GET_FLOAT64(op2);
} else if (tag2 == JS_TAG_INT) {
d2 = JS_VALUE_GET_INT(op2);
} else {
res = FALSE;
break;
}
number_test:
if (UNLIKELY(eq_mode >= JS_EQ_SAME_VALUE)) {
JSFloat64Union u1, u2;
/* NaN is not always normalized, so this test is necessary */
if (isnan(d1) || isnan(d2)) {
res = isnan(d1) == isnan(d2);
} else if (eq_mode == JS_EQ_SAME_VALUE_ZERO) {
res = (d1 == d2); /* +0 == -0 */
} else {
u1.d = d1;
u2.d = d2;
res = (u1.u64 == u2.u64); /* +0 != -0 */
}
} else {
res = (d1 == d2); /* if NaN return false and +0 == -0 */
}
goto done_no_free;
#ifdef CONFIG_BIGNUM
case JS_TAG_BIG_INT:
{
bf_t a_s, *a, b_s, *b;
if (tag1 != tag2) {
res = FALSE;
break;
}
a = JS_ToBigFloat(ctx, &a_s, op1);
b = JS_ToBigFloat(ctx, &b_s, op2);
res = bf_cmp_eq(a, b);
if (a == &a_s)
bf_delete(a);
if (b == &b_s)
bf_delete(b);
}
break;
case JS_TAG_BIG_FLOAT:
{
JSBigFloat *p1, *p2;
const bf_t *a, *b;
if (tag1 != tag2) {
res = FALSE;
break;
}
p1 = JS_VALUE_GET_PTR(op1);
p2 = JS_VALUE_GET_PTR(op2);
a = &p1->num;
b = &p2->num;
if (UNLIKELY(eq_mode >= JS_EQ_SAME_VALUE)) {
if (eq_mode == JS_EQ_SAME_VALUE_ZERO &&
a->expn == BF_EXP_ZERO && b->expn == BF_EXP_ZERO) {
res = TRUE;
} else {
res = (bf_cmp_full(a, b) == 0);
}
} else {
res = bf_cmp_eq(a, b);
}
}
break;
case JS_TAG_BIG_DECIMAL:
{
JSBigDecimal *p1, *p2;
const bfdec_t *a, *b;
if (tag1 != tag2) {
res = FALSE;
break;
}
p1 = JS_VALUE_GET_PTR(op1);
p2 = JS_VALUE_GET_PTR(op2);
a = &p1->num;
b = &p2->num;
res = bfdec_cmp_eq(a, b);
}
break;
#endif
default:
res = FALSE;
break;
}
JS_FreeValue(ctx, op1);
JS_FreeValue(ctx, op2);
done_no_free:
return res;
}
BOOL js_strict_eq(JSContext *ctx, JSValue op1, JSValue op2)
{
return js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT);
}
BOOL js_same_value(JSContext *ctx, JSValueConst op1, JSValueConst op2)
{
return js_strict_eq2(ctx,
JS_DupValue(ctx, op1), JS_DupValue(ctx, op2),
JS_EQ_SAME_VALUE);
}
BOOL js_same_value_zero(JSContext *ctx, JSValueConst op1, JSValueConst op2)
{
return js_strict_eq2(ctx,
JS_DupValue(ctx, op1), JS_DupValue(ctx, op2),
JS_EQ_SAME_VALUE_ZERO);
}
int js_strict_eq_slow(JSContext *ctx, JSValue *sp, BOOL is_neq)
{
BOOL res;
res = js_strict_eq(ctx, sp[-2], sp[-1]);
sp[-2] = JS_NewBool(ctx, res ^ is_neq);
return 0;
}
static BOOL tag_is_number(uint32_t tag)
{
return (tag == JS_TAG_INT || tag == JS_TAG_BIG_INT ||
tag == JS_TAG_FLOAT64 || tag == JS_TAG_BIG_FLOAT ||
tag == JS_TAG_BIG_DECIMAL);
}
static inline BOOL JS_IsHTMLDDA(JSContext *ctx, JSValueConst obj)
{
JSObject *p;
if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)
return FALSE;
p = JS_VALUE_GET_OBJ(obj);
return p->is_HTMLDDA;
}
int js_eq_slow(JSContext *ctx, JSValue *sp, BOOL is_neq)
{
#ifdef CONFIG_BIGNUM
JSValue op1, op2, ret;
int res;
uint32_t tag1, tag2;
op1 = sp[-2];
op2 = sp[-1];
redo:
tag1 = JS_VALUE_GET_NORM_TAG(op1);
tag2 = JS_VALUE_GET_NORM_TAG(op2);
if (tag_is_number(tag1) && tag_is_number(tag2)) {
if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) {
res = JS_VALUE_GET_INT(op1) == JS_VALUE_GET_INT(op2);
} else if ((tag1 == JS_TAG_FLOAT64 &&
(tag2 == JS_TAG_INT || tag2 == JS_TAG_FLOAT64)) ||
(tag2 == JS_TAG_FLOAT64 &&
(tag1 == JS_TAG_INT || tag1 == JS_TAG_FLOAT64))) {
double d1, d2;
if (tag1 == JS_TAG_FLOAT64) {
d1 = JS_VALUE_GET_FLOAT64(op1);
} else {
d1 = JS_VALUE_GET_INT(op1);
}
if (tag2 == JS_TAG_FLOAT64) {
d2 = JS_VALUE_GET_FLOAT64(op2);
} else {
d2 = JS_VALUE_GET_INT(op2);
}
res = (d1 == d2);
} else if (tag1 == JS_TAG_BIG_DECIMAL || tag2 == JS_TAG_BIG_DECIMAL) {
res = ctx->rt->bigdecimal_ops.compare(ctx, OP_eq, op1, op2);
if (res < 0)
goto exception;
} else if (tag1 == JS_TAG_BIG_FLOAT || tag2 == JS_TAG_BIG_FLOAT) {
res = ctx->rt->bigfloat_ops.compare(ctx, OP_eq, op1, op2);
if (res < 0)
goto exception;
} else {
res = ctx->rt->bigint_ops.compare(ctx, OP_eq, op1, op2);
if (res < 0)
goto exception;
}
} else if (tag1 == tag2) {
if (tag1 == JS_TAG_OBJECT) {
/* try the fallback operator */
res = js_call_binary_op_fallback(ctx, &ret, op1, op2,
is_neq ? OP_neq : OP_eq,
FALSE, HINT_NONE);
if (res != 0) {
JS_FreeValue(ctx, op1);
JS_FreeValue(ctx, op2);
if (res < 0) {
goto exception;
} else {
sp[-2] = ret;
return 0;
}
}
}
res = js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT);
} else if ((tag1 == JS_TAG_NULL && tag2 == JS_TAG_UNDEFINED) ||
(tag2 == JS_TAG_NULL && tag1 == JS_TAG_UNDEFINED)) {
res = TRUE;
} else if ((tag1 == JS_TAG_STRING && tag_is_number(tag2)) ||
(tag2 == JS_TAG_STRING && tag_is_number(tag1))) {
if ((tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT) &&
!is_math_mode(ctx)) {
if (tag1 == JS_TAG_STRING) {
op1 = JS_StringToBigInt(ctx, op1);
if (JS_VALUE_GET_TAG(op1) != JS_TAG_BIG_INT)
goto invalid_bigint_string;
}
if (tag2 == JS_TAG_STRING) {
op2 = JS_StringToBigInt(ctx, op2);
if (JS_VALUE_GET_TAG(op2) != JS_TAG_BIG_INT) {
invalid_bigint_string:
JS_FreeValue(ctx, op1);
JS_FreeValue(ctx, op2);
res = FALSE;
goto done;
}
}
} else {
op1 = JS_ToNumericFree(ctx, op1);
if (JS_IsException(op1)) {
JS_FreeValue(ctx, op2);
goto exception;
}
op2 = JS_ToNumericFree(ctx, op2);
if (JS_IsException(op2)) {
JS_FreeValue(ctx, op1);
goto exception;
}
}
res = js_strict_eq(ctx, op1, op2);
} else if (tag1 == JS_TAG_BOOL) {
op1 = JS_NewInt32(ctx, JS_VALUE_GET_INT(op1));
goto redo;
} else if (tag2 == JS_TAG_BOOL) {
op2 = JS_NewInt32(ctx, JS_VALUE_GET_INT(op2));
goto redo;
} else if ((tag1 == JS_TAG_OBJECT &&
(tag_is_number(tag2) || tag2 == JS_TAG_STRING || tag2 == JS_TAG_SYMBOL)) ||
(tag2 == JS_TAG_OBJECT &&
(tag_is_number(tag1) || tag1 == JS_TAG_STRING || tag1 == JS_TAG_SYMBOL))) {
/* try the fallback operator */
res = js_call_binary_op_fallback(ctx, &ret, op1, op2,
is_neq ? OP_neq : OP_eq,
FALSE, HINT_NONE);
if (res != 0) {
JS_FreeValue(ctx, op1);
JS_FreeValue(ctx, op2);
if (res < 0) {
goto exception;
} else {
sp[-2] = ret;
return 0;
}
}
op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE);
if (JS_IsException(op1)) {
JS_FreeValue(ctx, op2);
goto exception;
}
op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NONE);
if (JS_IsException(op2)) {
JS_FreeValue(ctx, op1);
goto exception;
}
goto redo;
} else {
/* IsHTMLDDA object is equivalent to undefined for '==' and '!=' */
if ((JS_IsHTMLDDA(ctx, op1) &&
(tag2 == JS_TAG_NULL || tag2 == JS_TAG_UNDEFINED)) ||
(JS_IsHTMLDDA(ctx, op2) &&
(tag1 == JS_TAG_NULL || tag1 == JS_TAG_UNDEFINED))) {
res = TRUE;
} else {
res = FALSE;
}
JS_FreeValue(ctx, op1);
JS_FreeValue(ctx, op2);
}
done:
sp[-2] = JS_NewBool(ctx, res ^ is_neq);
return 0;
exception:
sp[-2] = JS_UNDEFINED;
sp[-1] = JS_UNDEFINED;
return -1;
#else /* CONFIG_BIGNUM */
JSValue op1, op2;
int tag1, tag2;
BOOL res;
op1 = sp[-2];
op2 = sp[-1];
redo:
tag1 = JS_VALUE_GET_NORM_TAG(op1);
tag2 = JS_VALUE_GET_NORM_TAG(op2);
if (tag1 == tag2 ||
(tag1 == JS_TAG_INT && tag2 == JS_TAG_FLOAT64) ||
(tag2 == JS_TAG_INT && tag1 == JS_TAG_FLOAT64)) {
res = js_strict_eq(ctx, op1, op2);
} else if ((tag1 == JS_TAG_NULL && tag2 == JS_TAG_UNDEFINED) ||
(tag2 == JS_TAG_NULL && tag1 == JS_TAG_UNDEFINED)) {
res = TRUE;
} else if ((tag1 == JS_TAG_STRING && (tag2 == JS_TAG_INT ||
tag2 == JS_TAG_FLOAT64)) ||
(tag2 == JS_TAG_STRING && (tag1 == JS_TAG_INT ||
tag1 == JS_TAG_FLOAT64))) {
double d1;
double d2;
if (JS_ToFloat64Free(ctx, &d1, op1)) {
JS_FreeValue(ctx, op2);
goto exception;
}
if (JS_ToFloat64Free(ctx, &d2, op2))
goto exception;
res = (d1 == d2);
} else if (tag1 == JS_TAG_BOOL) {
op1 = JS_NewInt32(ctx, JS_VALUE_GET_INT(op1));
goto redo;
} else if (tag2 == JS_TAG_BOOL) {
op2 = JS_NewInt32(ctx, JS_VALUE_GET_INT(op2));
goto redo;
} else if (tag1 == JS_TAG_OBJECT &&
(tag2 == JS_TAG_INT || tag2 == JS_TAG_FLOAT64 || tag2 == JS_TAG_STRING || tag2 == JS_TAG_SYMBOL)) {
op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE);
if (JS_IsException(op1)) {
JS_FreeValue(ctx, op2);
goto exception;
}
goto redo;
} else if (tag2 == JS_TAG_OBJECT &&
(tag1 == JS_TAG_INT || tag1 == JS_TAG_FLOAT64 || tag1 == JS_TAG_STRING || tag1 == JS_TAG_SYMBOL)) {
op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NONE);
if (JS_IsException(op2)) {
JS_FreeValue(ctx, op1);
goto exception;
}
goto redo;
} else {
/* IsHTMLDDA object is equivalent to undefined for '==' and '!=' */
if ((JS_IsHTMLDDA(ctx, op1) &&
(tag2 == JS_TAG_NULL || tag2 == JS_TAG_UNDEFINED)) ||
(JS_IsHTMLDDA(ctx, op2) &&
(tag1 == JS_TAG_NULL || tag1 == JS_TAG_UNDEFINED))) {
res = TRUE;
} else {
res = FALSE;
}
JS_FreeValue(ctx, op1);
JS_FreeValue(ctx, op2);
}
sp[-2] = JS_NewBool(ctx, res ^ is_neq);
return 0;
exception:
sp[-2] = JS_UNDEFINED;
sp[-1] = JS_UNDEFINED;
return -1;
#endif /* CONFIG_BIGNUM */
}