Skip to content

Commit d272c1f

Browse files
kripkenradekdoulik
authored andcommitted
Fuzzer: Match the logging of i31ref between JS and C++ (WebAssembly#6335)
JS engines print i31ref as just a number, so we need a small regex to standardize the representation (similar to what we do for funcrefs on the code above). On the C++ side, make it actually print the i31ref rather than treat it like a generic reference (for whom we only print "object"). To do that we must unwrap an externalized i31 as necessary, and add a case for i31 in the printing logic. Also move that printing logic to its own function, as it was starting to get quite long.
1 parent 2b2a532 commit d272c1f

File tree

3 files changed

+68
-21
lines changed

3 files changed

+68
-21
lines changed

scripts/fuzz_opt.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,10 @@ def fix_double(x):
561561
# change that index, so ignore it
562562
out = re.sub(r'funcref\([\d\w$+-_:]+\)', 'funcref()', out)
563563

564+
# JS prints i31 as just a number, so change "i31ref(N)" (which C++ emits)
565+
# to "N".
566+
out = re.sub(r'i31ref\((\d+)\)', r'\1', out)
567+
564568
lines = out.splitlines()
565569
for i in range(len(lines)):
566570
line = lines[i]

src/tools/execution-results.h

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -115,27 +115,8 @@ struct ExecutionResults {
115115
// ignore the result if we hit an unreachable and returned no value
116116
if (values->size() > 0) {
117117
std::cout << "[fuzz-exec] note result: " << exp->name << " => ";
118-
auto resultType = func->getResults();
119-
if (resultType.isRef() && !resultType.isString()) {
120-
// Don't print reference values, as funcref(N) contains an index
121-
// for example, which is not guaranteed to remain identical after
122-
// optimizations. Do not print the type in detail (as even that
123-
// may change due to closed-world optimizations); just print a
124-
// simple type like JS does, 'object' or 'function', but also
125-
// print null for a null (so a null function does not get
126-
// printed as object, as in JS we have typeof null == 'object').
127-
if (values->size() == 1 && (*values)[0].isNull()) {
128-
std::cout << "null\n";
129-
} else if (resultType.isFunction()) {
130-
std::cout << "function\n";
131-
} else {
132-
std::cout << "object\n";
133-
}
134-
} else {
135-
// Non-references can be printed in full. So can strings, since we
136-
// always know how to print them and there is just one string
137-
// type.
138-
std::cout << *values << '\n';
118+
for (auto value : *values) {
119+
printValue(value);
139120
}
140121
}
141122
}
@@ -150,6 +131,39 @@ struct ExecutionResults {
150131
}
151132
}
152133

134+
void printValue(Literal value) {
135+
// Unwrap an externalized value to get the actual value.
136+
if (Type::isSubType(value.type, Type(HeapType::ext, Nullable))) {
137+
value = value.internalize();
138+
}
139+
140+
// Don't print most reference values, as e.g. funcref(N) contains an index,
141+
// which is not guaranteed to remain identical after optimizations. Do not
142+
// print the type in detail (as even that may change due to closed-world
143+
// optimizations); just print a simple type like JS does, 'object' or
144+
// 'function', but also print null for a null (so a null function does not
145+
// get printed as object, as in JS we have typeof null == 'object').
146+
//
147+
// The only references we print in full are strings and i31s, which have
148+
// simple and stable internal structures that optimizations will not alter.
149+
auto type = value.type;
150+
if (type.isRef()) {
151+
if (type.isString() || type.getHeapType() == HeapType::i31) {
152+
std::cout << value << '\n';
153+
} else if (value.isNull()) {
154+
std::cout << "null\n";
155+
} else if (type.isFunction()) {
156+
std::cout << "function\n";
157+
} else {
158+
std::cout << "object\n";
159+
}
160+
return;
161+
}
162+
163+
// Non-references can be printed in full.
164+
std::cout << value << '\n';
165+
}
166+
153167
// get current results and check them against previous ones
154168
void check(Module& wasm) {
155169
ExecutionResults optimizedResults;

test/lit/exec/i31.wast

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,27 @@
7676
(ref.null i31)
7777
)
7878
)
79+
80+
;; CHECK: [fuzz-exec] calling return-i31
81+
;; CHECK-NEXT: [fuzz-exec] note result: return-i31 => i31ref(42)
82+
(func $return-i31 (export "return-i31") (result i31ref)
83+
;; An i31 should be logged out using its integer value, unlike a struct or
84+
;; array which ends up as only "object".
85+
(ref.i31
86+
(i32.const 42)
87+
)
88+
)
89+
90+
;; CHECK: [fuzz-exec] calling return-exted-i31
91+
;; CHECK-NEXT: [fuzz-exec] note result: return-exted-i31 => i31ref(42)
92+
(func $return-exted-i31 (export "return-exted-i31") (result externref)
93+
;; Even an externalized i31 is logged out using its integer value.
94+
(extern.externalize
95+
(ref.i31
96+
(i32.const 42)
97+
)
98+
)
99+
)
79100
)
80101
;; CHECK: [fuzz-exec] calling null-local
81102
;; CHECK-NEXT: [fuzz-exec] note result: null-local => 1
@@ -97,10 +118,18 @@
97118

98119
;; CHECK: [fuzz-exec] calling trap
99120
;; CHECK-NEXT: [trap null ref]
121+
122+
;; CHECK: [fuzz-exec] calling return-i31
123+
;; CHECK-NEXT: [fuzz-exec] note result: return-i31 => i31ref(42)
124+
125+
;; CHECK: [fuzz-exec] calling return-exted-i31
126+
;; CHECK-NEXT: [fuzz-exec] note result: return-exted-i31 => i31ref(42)
100127
;; CHECK-NEXT: [fuzz-exec] comparing nn-s
101128
;; CHECK-NEXT: [fuzz-exec] comparing nn-u
102129
;; CHECK-NEXT: [fuzz-exec] comparing non-null
103130
;; CHECK-NEXT: [fuzz-exec] comparing null-immediate
104131
;; CHECK-NEXT: [fuzz-exec] comparing null-local
132+
;; CHECK-NEXT: [fuzz-exec] comparing return-exted-i31
133+
;; CHECK-NEXT: [fuzz-exec] comparing return-i31
105134
;; CHECK-NEXT: [fuzz-exec] comparing trap
106135
;; CHECK-NEXT: [fuzz-exec] comparing zero-is-not-null

0 commit comments

Comments
 (0)