summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/literal.h11
-rw-r--r--src/wasm-interpreter.h3
-rw-r--r--src/wasm/literal.cpp12
-rw-r--r--test/example/cpp-unit.txt2
-rw-r--r--test/lit/exec/i31.wast106
5 files changed, 125 insertions, 9 deletions
diff --git a/src/literal.h b/src/literal.h
index c37f74499..1151abd2c 100644
--- a/src/literal.h
+++ b/src/literal.h
@@ -38,6 +38,9 @@ class Literal {
// store only integers, whose bits are deterministic. floats
// can have their signalling bit set, for example.
union {
+ // Note: i31 is stored in the |i32| field, with the lower 31 bits containing
+ // the value if there is one, and the highest bit containing whether there
+ // is a value. Thus, a null is |i32 === 0|.
int32_t i32;
int64_t i64;
uint8_t v128[16];
@@ -111,6 +114,9 @@ public:
if (isData()) {
return !gcData;
}
+ if (type.getHeapType() == HeapType::i31) {
+ return i32 == 0;
+ }
return true;
}
return false;
@@ -257,7 +263,7 @@ public:
}
static Literal makeI31(int32_t value) {
auto lit = Literal(Type(HeapType::i31, NonNullable));
- lit.i32 = value & 0x7fffffff;
+ lit.i32 = value | 0x80000000;
return lit;
}
@@ -276,7 +282,8 @@ public:
}
int32_t geti31(bool signed_ = true) const {
assert(type.getHeapType() == HeapType::i31);
- return signed_ ? (i32 << 1) >> 1 : i32;
+ // Cast to unsigned for the left shift to avoid undefined behavior.
+ return signed_ ? int32_t((uint32_t(i32) << 1)) >> 1 : (i32 & 0x7fffffff);
}
int64_t geti64() const {
assert(type == Type::i64);
diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h
index eb5dcd5a7..399910e7a 100644
--- a/src/wasm-interpreter.h
+++ b/src/wasm-interpreter.h
@@ -1419,6 +1419,9 @@ public:
}
const auto& value = flow.getSingleValue();
NOTE_EVAL1(value);
+ if (value.isNull()) {
+ trap("null ref");
+ }
return Literal(value.geti31(curr->signed_));
}
diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp
index 85e93f912..608018d8b 100644
--- a/src/wasm/literal.cpp
+++ b/src/wasm/literal.cpp
@@ -229,11 +229,7 @@ Literals Literal::makeNegOnes(Type type) {
Literal Literal::makeZero(Type type) {
assert(type.isSingle());
if (type.isRef()) {
- if (type.getHeapType() == HeapType::i31) {
- return makeI31(0);
- } else {
- return makeNull(type.getHeapType());
- }
+ return makeNull(type.getHeapType());
} else if (type.isRtt()) {
return Literal(type);
} else {
@@ -515,7 +511,11 @@ std::ostream& operator<<(std::ostream& o, Literal literal) {
o << "eqref(null)";
break;
case HeapType::i31:
- o << "i31ref(" << literal.geti31() << ")";
+ if (literal.isNull()) {
+ o << "i31ref(null)";
+ } else {
+ o << "i31ref(" << literal.geti31() << ")";
+ }
break;
case HeapType::func:
case HeapType::data:
diff --git a/test/example/cpp-unit.txt b/test/example/cpp-unit.txt
index cbe80e119..d1f1fc1fb 100644
--- a/test/example/cpp-unit.txt
+++ b/test/example/cpp-unit.txt
@@ -1,3 +1,3 @@
-i31ref(0)
+i31ref(null)
i31ref(0)
Success
diff --git a/test/lit/exec/i31.wast b/test/lit/exec/i31.wast
new file mode 100644
index 000000000..c8b43e6ce
--- /dev/null
+++ b/test/lit/exec/i31.wast
@@ -0,0 +1,106 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited.
+
+;; RUN: wasm-opt %s -all --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s
+
+(module
+ ;; CHECK: [fuzz-exec] calling null-local
+ ;; CHECK-NEXT: [fuzz-exec] note result: null-local => 1
+ (func "null-local" (result i32)
+ (local $ref (ref null i31))
+ (ref.is_null
+ (local.get $ref)
+ )
+ )
+
+ ;; CHECK: [fuzz-exec] calling null-immediate
+ ;; CHECK-NEXT: [fuzz-exec] note result: null-immediate => 1
+ (func "null-immediate" (result i32)
+ (ref.is_null
+ (ref.null i31)
+ )
+ )
+
+ ;; CHECK: [fuzz-exec] calling non-null
+ ;; CHECK-NEXT: [fuzz-exec] note result: non-null => 0
+ (func "non-null" (result i32)
+ (ref.is_null
+ (i31.new
+ (i32.const 1234)
+ )
+ )
+ )
+
+ ;; CHECK: [fuzz-exec] calling nn-u
+ ;; CHECK-NEXT: [fuzz-exec] note result: nn-u => 2147483647
+ (func "nn-u" (result i32)
+ (i31.get_u
+ (i31.new
+ (i32.const 0xffffffff)
+ )
+ )
+ )
+
+ ;; CHECK: [fuzz-exec] calling nn-s
+ ;; CHECK-NEXT: [fuzz-exec] note result: nn-s => -1
+ (func "nn-s" (result i32)
+ (i31.get_s
+ (i31.new
+ (i32.const 0xffffffff)
+ )
+ )
+ )
+
+ ;; CHECK: [fuzz-exec] calling zero-is-not-null
+ ;; CHECK-NEXT: [fuzz-exec] note result: zero-is-not-null => 0
+ (func "zero-is-not-null" (result i32)
+ (local $ref (ref null i31))
+ (local.set $ref
+ (i31.new
+ (i32.const 0)
+ )
+ )
+ (i32.add ;; 0 + 0 is 0
+ (ref.is_null
+ (local.get $ref)
+ )
+ (i31.get_u ;; this should not trap on null
+ (local.get $ref)
+ )
+ )
+ )
+
+ ;; CHECK: [fuzz-exec] calling trap
+ ;; CHECK-NEXT: [trap null ref]
+ (func "trap" (result i32)
+ (i31.get_u
+ (ref.null i31)
+ )
+ )
+)
+;; CHECK: [fuzz-exec] calling null-local
+;; CHECK-NEXT: [fuzz-exec] note result: null-local => 1
+
+;; CHECK: [fuzz-exec] calling null-immediate
+;; CHECK-NEXT: [fuzz-exec] note result: null-immediate => 1
+
+;; CHECK: [fuzz-exec] calling non-null
+;; CHECK-NEXT: [fuzz-exec] note result: non-null => 0
+
+;; CHECK: [fuzz-exec] calling nn-u
+;; CHECK-NEXT: [fuzz-exec] note result: nn-u => 2147483647
+
+;; CHECK: [fuzz-exec] calling nn-s
+;; CHECK-NEXT: [fuzz-exec] note result: nn-s => -1
+
+;; CHECK: [fuzz-exec] calling zero-is-not-null
+;; CHECK-NEXT: [fuzz-exec] note result: zero-is-not-null => 0
+
+;; CHECK: [fuzz-exec] calling trap
+;; CHECK-NEXT: [trap null ref]
+;; CHECK-NEXT: [fuzz-exec] comparing nn-s
+;; CHECK-NEXT: [fuzz-exec] comparing nn-u
+;; CHECK-NEXT: [fuzz-exec] comparing non-null
+;; CHECK-NEXT: [fuzz-exec] comparing null-immediate
+;; CHECK-NEXT: [fuzz-exec] comparing null-local
+;; CHECK-NEXT: [fuzz-exec] comparing trap
+;; CHECK-NEXT: [fuzz-exec] comparing zero-is-not-null