Skip to content

Commit

Permalink
Fuzzer: Match the logging of i31ref between JS and C++ (#6335)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
kripken authored Feb 22, 2024
1 parent f6bb943 commit 4969f93
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 21 deletions.
4 changes: 4 additions & 0 deletions scripts/fuzz_opt.py
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,10 @@ def fix_double(x):
# change that index, so ignore it
out = re.sub(r'funcref\([\d\w$+-_:]+\)', 'funcref()', out)

# JS prints i31 as just a number, so change "i31ref(N)" (which C++ emits)
# to "N".
out = re.sub(r'i31ref\((\d+)\)', r'\1', out)

lines = out.splitlines()
for i in range(len(lines)):
line = lines[i]
Expand Down
56 changes: 35 additions & 21 deletions src/tools/execution-results.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,27 +115,8 @@ struct ExecutionResults {
// ignore the result if we hit an unreachable and returned no value
if (values->size() > 0) {
std::cout << "[fuzz-exec] note result: " << exp->name << " => ";
auto resultType = func->getResults();
if (resultType.isRef() && !resultType.isString()) {
// Don't print reference values, as funcref(N) contains an index
// for example, which is not guaranteed to remain identical after
// optimizations. Do not print the type in detail (as even that
// may change due to closed-world optimizations); just print a
// simple type like JS does, 'object' or 'function', but also
// print null for a null (so a null function does not get
// printed as object, as in JS we have typeof null == 'object').
if (values->size() == 1 && (*values)[0].isNull()) {
std::cout << "null\n";
} else if (resultType.isFunction()) {
std::cout << "function\n";
} else {
std::cout << "object\n";
}
} else {
// Non-references can be printed in full. So can strings, since we
// always know how to print them and there is just one string
// type.
std::cout << *values << '\n';
for (auto value : *values) {
printValue(value);
}
}
}
Expand All @@ -150,6 +131,39 @@ struct ExecutionResults {
}
}

void printValue(Literal value) {
// Unwrap an externalized value to get the actual value.
if (Type::isSubType(value.type, Type(HeapType::ext, Nullable))) {
value = value.internalize();
}

// Don't print most reference values, as e.g. funcref(N) contains an index,
// which is not guaranteed to remain identical after optimizations. Do not
// print the type in detail (as even that may change due to closed-world
// optimizations); just print a simple type like JS does, 'object' or
// 'function', but also print null for a null (so a null function does not
// get printed as object, as in JS we have typeof null == 'object').
//
// The only references we print in full are strings and i31s, which have
// simple and stable internal structures that optimizations will not alter.
auto type = value.type;
if (type.isRef()) {
if (type.isString() || type.getHeapType() == HeapType::i31) {
std::cout << value << '\n';
} else if (value.isNull()) {
std::cout << "null\n";
} else if (type.isFunction()) {
std::cout << "function\n";
} else {
std::cout << "object\n";
}
return;
}

// Non-references can be printed in full.
std::cout << value << '\n';
}

// get current results and check them against previous ones
void check(Module& wasm) {
ExecutionResults optimizedResults;
Expand Down
29 changes: 29 additions & 0 deletions test/lit/exec/i31.wast
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,27 @@
(ref.null i31)
)
)

;; CHECK: [fuzz-exec] calling return-i31
;; CHECK-NEXT: [fuzz-exec] note result: return-i31 => i31ref(42)
(func $return-i31 (export "return-i31") (result i31ref)
;; An i31 should be logged out using its integer value, unlike a struct or
;; array which ends up as only "object".
(ref.i31
(i32.const 42)
)
)

;; CHECK: [fuzz-exec] calling return-exted-i31
;; CHECK-NEXT: [fuzz-exec] note result: return-exted-i31 => i31ref(42)
(func $return-exted-i31 (export "return-exted-i31") (result externref)
;; Even an externalized i31 is logged out using its integer value.
(extern.externalize
(ref.i31
(i32.const 42)
)
)
)
)
;; CHECK: [fuzz-exec] calling null-local
;; CHECK-NEXT: [fuzz-exec] note result: null-local => 1
Expand All @@ -97,10 +118,18 @@

;; CHECK: [fuzz-exec] calling trap
;; CHECK-NEXT: [trap null ref]

;; CHECK: [fuzz-exec] calling return-i31
;; CHECK-NEXT: [fuzz-exec] note result: return-i31 => i31ref(42)

;; CHECK: [fuzz-exec] calling return-exted-i31
;; CHECK-NEXT: [fuzz-exec] note result: return-exted-i31 => i31ref(42)
;; 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 return-exted-i31
;; CHECK-NEXT: [fuzz-exec] comparing return-i31
;; CHECK-NEXT: [fuzz-exec] comparing trap
;; CHECK-NEXT: [fuzz-exec] comparing zero-is-not-null

0 comments on commit 4969f93

Please sign in to comment.