diff --git a/doc/ext_ffi_semantics.html b/doc/ext_ffi_semantics.html
index 2dbe41e9..304befa7 100644
--- a/doc/ext_ffi_semantics.html
+++ b/doc/ext_ffi_semantics.html
@@ -724,6 +724,11 @@ of them is an uint64_t, the other side is converted to an
both sides are converted to an int64_t and a signed
comparison is performed.
+
Comparisons for equality/inequality never raise an error.
+Even incompatible pointers can be compared for equality by address. Any
+other incompatible comparison (also with non-cdata objects) treats the
+two sides as unequal.
+
cdata objects as table keys
diff --git a/src/lj_carith.c b/src/lj_carith.c
index 56708bf6..583a3876 100644
--- a/src/lj_carith.c
+++ b/src/lj_carith.c
@@ -205,6 +205,10 @@ static int lj_carith_meta(lua_State *L, CTState *cts, CDArith *ca, MMS mm)
if (!tv) {
const char *repr[2];
int i;
+ if (mm == MM_eq) { /* Equality checks never raise an error. */
+ setboolV(L->top-1, 0);
+ return 1;
+ }
for (i = 0; i < 2; i++) {
if (ca->ct[i])
repr[i] = strdata(lj_ctype_repr(L, ctype_typeid(cts, ca->ct[i]), NULL));
diff --git a/src/lj_crecord.c b/src/lj_crecord.c
index 81ab3540..74d62d6c 100644
--- a/src/lj_crecord.c
+++ b/src/lj_crecord.c
@@ -1073,13 +1073,17 @@ static void crec_arith_meta(jit_State *J, CTState *cts, RecordFFData *rd)
tv = lj_ctype_meta(cts, argv2cdata(J, J->base[1], &rd->argv[1])->typeid,
(MMS)rd->data);
}
- if (tv && tvisfunc(tv)) {
- J->base[-1] = lj_ir_kfunc(J, funcV(tv)) | TREF_FRAME;
- rd->nres = -1; /* Pending tailcall. */
- } else {
- /* NYI: non-function metamethods. */
- lj_trace_err(J, LJ_TRERR_BADTYPE);
+ if (tv) {
+ if (tvisfunc(tv)) {
+ J->base[-1] = lj_ir_kfunc(J, funcV(tv)) | TREF_FRAME;
+ rd->nres = -1; /* Pending tailcall. */
+ return;
+ } /* NYI: non-function metamethods. */
+ } else if ((MMS)rd->data == MM_eq) {
+ J->base[0] = TREF_FALSE;
+ return;
}
+ lj_trace_err(J, LJ_TRERR_BADTYPE);
}
void LJ_FASTCALL recff_cdata_arith(jit_State *J, RecordFFData *rd)