Skip to content

Commit db84777

Browse files
committed
fix: x86_64 string comparison ABI mismatch — js_string_equals returned bool not i32
js_string_equals and js_bigint_eq were declared as returning bool (1 byte in al register on x86_64) but the Cranelift codegen declared them as returning I32 (reads full eax). On x86_64 Linux, the upper 24 bits of eax contain garbage from previous operations, making every string === comparison return true. This broke typeof checks, switch/case on strings, and any string equality in compiled code. Changed both functions to return i32 with explicit 0/1 values, and updated all internal callers to handle i32 returns. Fixes Bug #12 ABI string generation returning empty on x86_64.
1 parent 10dead0 commit db84777

4 files changed

Lines changed: 21 additions & 22 deletions

File tree

crates/perry-runtime/src/bigint.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -792,14 +792,14 @@ pub extern "C" fn js_bigint_cmp(a: *const BigIntHeader, b: *const BigIntHeader)
792792
/// Check if two BigInts are equal
793793
#[no_mangle]
794794
#[inline(never)]
795-
pub extern "C" fn js_bigint_eq(a: *const BigIntHeader, b: *const BigIntHeader) -> bool {
795+
pub extern "C" fn js_bigint_eq(a: *const BigIntHeader, b: *const BigIntHeader) -> i32 {
796796
let a = clean_bigint_ptr(a);
797797
let b = clean_bigint_ptr(b);
798798
if a.is_null() || b.is_null() {
799-
return a == b; // both null = equal, one null = not equal
799+
return if a == b { 1 } else { 0 }; // both null = equal, one null = not equal
800800
}
801801
unsafe {
802-
(*a).limbs == (*b).limbs
802+
if (*a).limbs == (*b).limbs { 1 } else { 0 }
803803
}
804804
}
805805

crates/perry-runtime/src/object.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -968,7 +968,7 @@ pub extern "C" fn js_object_has_property(obj: f64, key: f64) -> f64 {
968968
let stored_key_val = crate::array::js_array_get(keys, i as u32);
969969
if stored_key_val.is_string() {
970970
let stored_key = stored_key_val.as_string_ptr();
971-
if crate::string::js_string_equals(key_str, stored_key) {
971+
if crate::string::js_string_equals(key_str, stored_key) != 0 {
972972
// Check if the field was deleted (set to undefined by delete operator)
973973
let field_val = js_object_get_field(obj_ptr, i as u32);
974974
if field_val.is_undefined() {
@@ -1120,7 +1120,7 @@ pub extern "C" fn js_object_get_field_by_name(obj: *const ObjectHeader, key: *co
11201120
let key_val = crate::array::js_array_get(keys, i as u32);
11211121
if key_val.is_string() {
11221122
let stored_key = key_val.as_string_ptr();
1123-
if crate::string::js_string_equals(key, stored_key) {
1123+
if crate::string::js_string_equals(key, stored_key) != 0 {
11241124
// Cache this lookup for next time
11251125
FIELD_CACHE.with(|c| {
11261126
let cache = &mut *c.get();
@@ -1266,7 +1266,7 @@ pub extern "C" fn js_object_set_field_by_name(obj: *mut ObjectHeader, key: *cons
12661266
// Keys are stored as string pointers (NaN-boxed)
12671267
if key_val.is_string() {
12681268
let stored_key = key_val.as_string_ptr();
1269-
if crate::string::js_string_equals(key, stored_key) {
1269+
if crate::string::js_string_equals(key, stored_key) != 0 {
12701270
// Found it - update the field
12711271
if i < alloc_limit {
12721272
js_object_set_field(obj, i as u32, JSValue::from_bits(value.to_bits()));
@@ -1365,7 +1365,7 @@ pub extern "C" fn js_object_delete_field(obj: *mut ObjectHeader, key: *const cra
13651365
// Keys are stored as string pointers (NaN-boxed)
13661366
if key_val.is_string() {
13671367
let stored_key = key_val.as_string_ptr();
1368-
if crate::string::js_string_equals(key, stored_key) {
1368+
if crate::string::js_string_equals(key, stored_key) != 0 {
13691369
// Found it - set the field to undefined
13701370
js_object_set_field(obj, i as u32, JSValue::undefined());
13711371
return 1;
@@ -1443,7 +1443,7 @@ pub extern "C" fn js_object_rest(src: *const ObjectHeader, exclude_keys: *const
14431443
let ex_val = crate::array::js_array_get(exclude_keys, j as u32);
14441444
if ex_val.is_string() {
14451445
let ex_str = ex_val.as_string_ptr();
1446-
if crate::string::js_string_equals(key_str, ex_str) {
1446+
if crate::string::js_string_equals(key_str, ex_str) != 0 {
14471447
excluded = true;
14481448
break;
14491449
}
@@ -1699,7 +1699,7 @@ pub unsafe extern "C" fn js_native_call_method(
16991699
let key_val = crate::array::js_array_get(keys, i as u32);
17001700
if key_val.is_string() {
17011701
let stored_key = key_val.as_string_ptr();
1702-
if crate::string::js_string_equals(method_key, stored_key) {
1702+
if crate::string::js_string_equals(method_key, stored_key) != 0 {
17031703
let field_val = js_object_get_field(obj as *mut _, i as u32);
17041704
if field_val.is_pointer() {
17051705
return crate::closure::js_native_call_value(
@@ -1827,7 +1827,7 @@ pub unsafe extern "C" fn js_native_call_method(
18271827
let key_val = crate::array::js_array_get(keys, i as u32);
18281828
if key_val.is_string() {
18291829
let stored_key = key_val.as_string_ptr();
1830-
if crate::string::js_string_equals(method_key, stored_key) {
1830+
if crate::string::js_string_equals(method_key, stored_key) != 0 {
18311831
let field_val = js_object_get_field(obj as *mut _, i as u32);
18321832
if field_val.is_pointer() {
18331833
return crate::closure::js_native_call_value(
@@ -1989,7 +1989,7 @@ pub unsafe extern "C" fn js_native_call_method(
19891989
let key_val = crate::array::js_array_get(keys, i as u32);
19901990
if key_val.is_string() {
19911991
let stored_key = key_val.as_string_ptr();
1992-
if crate::string::js_string_equals(method_key, stored_key) {
1992+
if crate::string::js_string_equals(method_key, stored_key) != 0 {
19931993
// Found the method - get it and call it if it's a closure
19941994
let field_val = js_object_get_field(obj as *mut _, i as u32);
19951995
if field_val.is_pointer() {
@@ -2487,7 +2487,7 @@ unsafe fn dispatch_bigint_binary_method(
24872487
// Comparison → returns boolean/number
24882488
"eq" => {
24892489
let result = crate::bigint::js_bigint_eq(a, b);
2490-
return f64::from_bits(JSValue::bool(result).bits());
2490+
return f64::from_bits(JSValue::bool(result != 0).bits());
24912491
}
24922492
"lt" => {
24932493
let result = crate::bigint::js_bigint_cmp(a, b);

crates/perry-runtime/src/string.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -439,34 +439,34 @@ pub extern "C" fn js_string_compare(a: *const StringHeader, b: *const StringHead
439439

440440
/// Compare two strings for equality
441441
#[no_mangle]
442-
pub extern "C" fn js_string_equals(a: *const StringHeader, b: *const StringHeader) -> bool {
442+
pub extern "C" fn js_string_equals(a: *const StringHeader, b: *const StringHeader) -> i32 {
443443
// Pointer identity fast path
444444
if std::ptr::eq(a, b) {
445-
return true;
445+
return 1;
446446
}
447447

448448
let a_valid = is_valid_string_ptr(a);
449449
let b_valid = is_valid_string_ptr(b);
450450
if !a_valid && !b_valid {
451-
return true;
451+
return 1;
452452
}
453453
if !a_valid || !b_valid {
454-
return false;
454+
return 0;
455455
}
456456

457457
let len_a = unsafe { (*a).length };
458458
let len_b = unsafe { (*b).length };
459459

460460
if len_a != len_b {
461-
return false;
461+
return 0;
462462
}
463463

464464
unsafe {
465465
let data_a = string_data(a);
466466
let data_b = string_data(b);
467467
let slice_a = std::slice::from_raw_parts(data_a, len_a as usize);
468468
let slice_b = std::slice::from_raw_parts(data_b, len_b as usize);
469-
slice_a == slice_b
469+
if slice_a == slice_b { 1 } else { 0 }
470470
}
471471
}
472472

crates/perry-runtime/src/value.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -965,15 +965,14 @@ pub extern "C" fn js_jsvalue_equals(a: f64, b: f64) -> i32 {
965965
if a_val.is_bigint() && b_val.is_bigint() {
966966
let a_ptr = a_val.as_bigint_ptr();
967967
let b_ptr = b_val.as_bigint_ptr();
968-
let result = crate::bigint::js_bigint_eq(a_ptr, b_ptr);
969-
return if result { 1 } else { 0 };
968+
return crate::bigint::js_bigint_eq(a_ptr, b_ptr);
970969
}
971970

972971
// String comparison: compare by content, not by pointer
973972
if a_val.is_string() && b_val.is_string() {
974973
let a_str = a_val.as_string_ptr();
975974
let b_str = b_val.as_string_ptr();
976-
return if crate::string::js_string_equals(a_str, b_str) { 1 } else { 0 };
975+
return crate::string::js_string_equals(a_str, b_str);
977976
}
978977

979978
// Helper: check if bits represent a plain IEEE 754 number (not a NaN-boxed tagged value).
@@ -1181,7 +1180,7 @@ pub extern "C" fn js_dynamic_string_equals(a: f64, b: f64) -> i32 {
11811180
return 0;
11821181
}
11831182

1184-
if crate::string::js_string_equals(a_ptr, b_ptr) {
1183+
if crate::string::js_string_equals(a_ptr, b_ptr) != 0 {
11851184
1
11861185
} else {
11871186
0

0 commit comments

Comments
 (0)