2121#include < deque>
2222#include < memory>
2323
24+ #include " ir/import-names.h"
2425#include " ir/import-utils.h"
2526#include " shell-interface.h"
2627#include " support/utilities.h"
@@ -58,20 +59,9 @@ Tag& getJsTag() {
5859 return tag;
5960}
6061
61- void printValue (Literal value) {
62- // Unwrap an externalized GC value to get the actual value, but not strings,
63- // which are normally a subtype of ext.
64- if (Type::isSubType (value.type , Type (HeapType::ext, Nullable)) &&
65- !value.type .isString ()) {
66- value = value.internalize ();
67- }
68-
69- // An anyref literal is a string.
70- if (value.type .isRef () &&
71- value.type .getHeapType ().isMaybeShared (HeapType::any)) {
72- value = value.externalize ();
73- }
62+ constexpr Index jsErrorPayload = 0xbad ;
7463
64+ void printValue (Literal value) {
7565 // Don't print most reference values, as e.g. funcref(N) contains an index,
7666 // which is not guaranteed to remain identical after optimizations. Do not
7767 // print the type in detail (as even that may change due to closed-world
@@ -81,22 +71,32 @@ void printValue(Literal value) {
8171 //
8272 // The only references we print in full are strings and i31s, which have
8373 // simple and stable internal structures that optimizations will not alter.
84- auto type = value.type ;
85- if (type.isRef ()) {
86- if (type.isString () || type.getHeapType ().isMaybeShared (HeapType::i31)) {
87- std::cout << value;
88- } else if (value.isNull ()) {
89- std::cout << " null" ;
90- } else if (type.isFunction ()) {
91- std::cout << " function" ;
92- } else {
93- std::cout << " object" ;
94- }
74+ //
75+ // Non-references can be printed in full.
76+ if (!value.type .isRef ()) {
77+ std::cout << value;
9578 return ;
9679 }
97-
98- // Non-references can be printed in full.
99- std::cout << value;
80+ value = value.unwrap ();
81+ auto heapType = value.type .getHeapType ();
82+ if (heapType.isMaybeShared (HeapType::ext) &&
83+ value.getExternPayload () == jsErrorPayload) {
84+ std::cout << " jserror" ;
85+ return ;
86+ }
87+ if (heapType.isString () || heapType.isMaybeShared (HeapType::ext) ||
88+ heapType.isMaybeShared (HeapType::i31)) {
89+ std::cout << value;
90+ } else if (value.isNull ()) {
91+ std::cout << " null" ;
92+ } else if (heapType.isFunction ()) {
93+ std::cout << " function" ;
94+ } else {
95+ // Print 'object' and its JS-visible prototype, which may be null.
96+ std::cout << " object(" ;
97+ printValue (value.getJSPrototype ());
98+ std::cout << ' )' ;
99+ }
100100}
101101
102102} // namespace
@@ -275,7 +275,7 @@ struct LoggingExternalInterface : public ShellExternalInterface {
275275
276276 void throwJSException () {
277277 // JS exceptions contain an externref.
278- Literals arguments = {Literal::makeExtern (0 , Unshared)};
278+ Literals arguments = {Literal::makeExtern (jsErrorPayload , Unshared)};
279279 auto payload = std::make_shared<ExnData>(&jsTag, arguments);
280280 throwException (WasmException{Literal (payload)});
281281 }
@@ -403,8 +403,18 @@ class FuzzerImportResolver
403403 if (mut || !type.isRef () || type.getHeapType () != HeapType::ext) {
404404 return nullptr ;
405405 }
406- // TODO: Generate a distinct payload for each global.
407- synthesizedGlobals.emplace_back (Literals{Literal::makeExtern (0 , Unshared)});
406+ // Optimizations may reorder or remove imports, so we need a distinct
407+ // payload that is independent of the import order. Just compute a simple
408+ // payload integer from the import names. This must be kept in sync with
409+ // fuzz_shell.js.
410+ Index payload = 0 ;
411+ for (auto name : {name.module , name.name }) {
412+ for (auto c : name.str ) {
413+ payload = (payload + static_cast <Index>(c)) % 251 ;
414+ }
415+ }
416+ synthesizedGlobals.emplace_back (
417+ Literals{Literal::makeExtern (payload, Unshared)});
408418 return &synthesizedGlobals.back ();
409419 }
410420
@@ -527,28 +537,56 @@ struct ExecutionResults {
527537 }
528538
529539 bool areEqual (Literal a, Literal b) {
530- // Don't compare references. There are several issues here that we can't
531- // fully handle, see https://github.com/WebAssembly/binaryen/issues/3378,
532- // but the core issue is that since we optimize assuming a closed world, the
533- // types and structure of GC data can arbitrarily change after
534- // optimizations, even in ways that are externally visible from outside
535- // the module.
536- //
537- // We can, however, compare strings as they refer to simple data that has a
538- // consistent representation (the same reasons as why we can print them in
539- // printValue(), above).
540+ // Only compare some references. In general the optimizer may change
541+ // identities and structures of functions, types, and GC values in ways that
542+ // are not externally observable. We must therefore limit ourselves to
543+ // comparing information that _is_ externally observable.
540544 //
541- // TODO: Once we support optimizing under some form of open-world
542- // assumption, we should be able to check that the types and/or structure of
543- // GC data passed out of the module does not change.
544- if (a.type .isRef () && !a.type .isString () &&
545- !a.type .getHeapType ().isMaybeShared (HeapType::i31)) {
546- return true ;
545+ // TODO: We could compare more information when we know it will be
546+ // externally visible, for example when the type of the value is public.
547+ if (!a.type .isRef () || !b.type .isRef ()) {
548+ return a == b;
549+ }
550+ // The environment always sees externalized references and is able to
551+ // observe the difference between external references and externalized
552+ // internal references. Make sure this is accounted for below by unwrapping
553+ // the references.
554+ a = a.unwrap ();
555+ b = b.unwrap ();
556+ auto htA = a.type .getHeapType ();
557+ auto htB = b.type .getHeapType ();
558+ // What type hierarchy a heap type is in is generally observable.
559+ if (htA.getTop () != htB.getTop ()) {
560+ return false ;
547561 }
548- if (a != b) {
549- std::cout << " values not identical! " << a << " != " << b << ' \n ' ;
562+ // Null values are observable.
563+ if (htA.isBottom () || htB.isBottom ()) {
564+ return a == b;
565+ }
566+ // String values are observable.
567+ if (htA.isString () || htB.isString ()) {
568+ return a == b;
569+ }
570+ // i31 values are observable.
571+ if (htA.isMaybeShared (HeapType::i31) || htB.isMaybeShared (HeapType::i31)) {
572+ return a == b;
573+ }
574+ // External references are observable. (These cannot be externalized
575+ // internal references because they've already been unwrapped.)
576+ if (htA.isMaybeShared (HeapType::ext) || htB.isMaybeShared (HeapType::ext)) {
577+ return a == b;
578+ }
579+ // Configured prototypes are observable. Even if they are also opaque Wasm
580+ // references, their having different pointer identities is observable.
581+ // However, we have no way of comparing pointer identities across
582+ // executions, so just recursively look for externally observable
583+ // differences in the prototypes.
584+ if (!areEqual (a.getJSPrototype (), b.getJSPrototype ())) {
550585 return false ;
551586 }
587+
588+ // Other differences are not observable, so conservatively consider the
589+ // values equal.
552590 return true ;
553591 }
554592
@@ -559,6 +597,7 @@ struct ExecutionResults {
559597 }
560598 for (Index i = 0 ; i < a.size (); i++) {
561599 if (!areEqual (a[i], b[i])) {
600+ std::cout << " values not identical! " << a[i] << " != " << b[i] << ' \n ' ;
562601 return false ;
563602 }
564603 }
@@ -628,7 +667,13 @@ struct ExecutionResults {
628667 } catch (const TrapException&) {
629668 return Trap{};
630669 } catch (const WasmException& e) {
631- std::cout << " [exception thrown: " << e << " ]" << std::endl;
670+ auto & exn = *e.exn .getExnData ();
671+ std::cout << " [exception thrown: " << exn.tag ->name ;
672+ for (auto val : exn.payload ) {
673+ std::cout << ' ' ;
674+ printValue (val);
675+ }
676+ std::cout << " ]" << std::endl;
632677 return Exception{};
633678 } catch (const HostLimitException&) {
634679 // This should be ignored and not compared with, as optimizations can
0 commit comments