diff options
-rw-r--r-- | src/ir/possible-contents.cpp | 69 | ||||
-rw-r--r-- | src/ir/possible-contents.h | 5 | ||||
-rw-r--r-- | test/lit/passes/gufa-refs.wast | 261 |
3 files changed, 334 insertions, 1 deletions
diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index d004ef70e..7a3cdc505 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -1999,6 +1999,8 @@ private: const GlobalLocation& globalLoc); void filterDataContents(PossibleContents& contents, const DataLocation& dataLoc); + void filterPackedDataReads(PossibleContents& contents, + const ExpressionLocation& exprLoc); // Reads from GC data: a struct.get or array.get. This is given the type of // the read operation, the field that is read on that type, the known contents @@ -2301,10 +2303,24 @@ bool Flower::updateContents(LocationIndex locationIndex, if (auto* dataLoc = std::get_if<DataLocation>(&location)) { filterDataContents(newContents, *dataLoc); #if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2 - std::cout << " pre-filtered contents:\n"; + std::cout << " pre-filtered data contents:\n"; newContents.dump(std::cout, &wasm); std::cout << '\n'; #endif + } else if (auto* exprLoc = std::get_if<ExpressionLocation>(&location)) { + if (exprLoc->expr->is<StructGet>() || exprLoc->expr->is<ArrayGet>()) { + // Packed data reads must be filtered before the combine() operation, as + // we must only combine the filtered contents (e.g. if 0xff arrives which + // as a signed read is truly 0xffffffff then we cannot first combine the + // existing 0xffffffff with the new 0xff, as they are different, and the + // result will no longer be a constant). + filterPackedDataReads(newContents, *exprLoc); +#if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2 + std::cout << " pre-filtered packed read contents:\n"; + newContents.dump(std::cout, &wasm); + std::cout << '\n'; +#endif + } } contents.combine(newContents); @@ -2633,6 +2649,57 @@ void Flower::filterDataContents(PossibleContents& contents, } } +void Flower::filterPackedDataReads(PossibleContents& contents, + const ExpressionLocation& exprLoc) { + auto* expr = exprLoc.expr; + + // Packed fields are stored as the truncated bits (see comment on + // DataLocation; the actual truncation is done in filterDataContents), which + // means that unsigned gets just work but signed ones need fixing (and we only + // know how to do that here, when we reach the get and see if it is signed). + auto signed_ = false; + Expression* ref; + Index index; + if (auto* get = expr->dynCast<StructGet>()) { + signed_ = get->signed_; + ref = get->ref; + index = get->index; + } else if (auto* get = expr->dynCast<ArrayGet>()) { + signed_ = get->signed_; + ref = get->ref; + // Arrays are treated as having a single field. + index = 0; + } else { + WASM_UNREACHABLE("bad packed read"); + } + if (!signed_) { + return; + } + + // We are reading data here, so the reference must be a valid struct or + // array, otherwise we would never have gotten here. + assert(ref->type.isRef()); + auto field = GCTypeUtils::getField(ref->type.getHeapType(), index); + assert(field); + if (!field->isPacked()) { + return; + } + + if (contents.isLiteral()) { + // This is a constant. We can sign-extend it and use that value. + auto shifts = Literal(int32_t(32 - field->getByteSize() * 8)); + auto lit = contents.getLiteral(); + lit = lit.shl(shifts); + lit = lit.shrS(shifts); + contents = PossibleContents::literal(lit); + } else { + // This is not a constant. As in filterDataContents, give up and leave + // only the type, since we have no way to track the sign-extension on + // top of whatever this is. + contents = PossibleContents::fromType(contents.getType()); + } +} + void Flower::readFromData(Type declaredType, Index fieldIndex, const PossibleContents& refContents, diff --git a/src/ir/possible-contents.h b/src/ir/possible-contents.h index 7ff0f1a07..5ec4f758f 100644 --- a/src/ir/possible-contents.h +++ b/src/ir/possible-contents.h @@ -446,6 +446,11 @@ struct SignatureResultLocation { // The location of contents in a struct or array (i.e., things that can fit in a // dataref). Note that this is specific to this type - it does not include data // about subtypes or supertypes. +// +// We store the truncated bits here when the field is packed. That is, if -1 is +// written to an i8 then the value here will be 0xff. StructGet/ArrayGet +// operations that read a signed value must then perform a sign-extend +// operation. struct DataLocation { HeapType type; // The index of the field in a struct, or 0 for an array (where we do not diff --git a/test/lit/passes/gufa-refs.wast b/test/lit/passes/gufa-refs.wast index 4be45e4ca..9a32c1acb 100644 --- a/test/lit/passes/gufa-refs.wast +++ b/test/lit/passes/gufa-refs.wast @@ -5622,6 +5622,267 @@ ) ) +;; Packed fields with signed gets. +(module + ;; CHECK: (type $array (array (mut i8))) + + ;; CHECK: (type $1 (func)) + + ;; CHECK: (type $struct (struct (field i16))) + (type $struct (struct (field i16))) + + (type $array (array (mut i8))) + + ;; CHECK: (func $test-struct (type $1) + ;; CHECK-NEXT: (local $x (ref $struct)) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 65535) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test-struct + (local $x (ref $struct)) + (local.set $x + (struct.new $struct + (i32.const -1) + ) + ) + ;; This reads -1. + (drop + (struct.get_s $struct 0 + (local.get $x) + ) + ) + ;; This reads 65535, as the other bits were truncated. + (drop + (struct.get_u $struct 0 + (local.get $x) + ) + ) + ) + + ;; CHECK: (func $test-array (type $1) + ;; CHECK-NEXT: (local $x (ref $array)) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (array.new_fixed $array 1 + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (array.get_s $array + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (array.get_u $array + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test-array + (local $x (ref $array)) + (local.set $x + (array.new_fixed $array 1 + (i32.const -1) + ) + ) + ;; This reads -1. + (drop + (array.get_s $array + (local.get $x) + (i32.const 0) + ) + ) + ;; This reads 255, as the other bits were truncated. + (drop + (array.get_u $array + (local.get $x) + (i32.const 0) + ) + ) + ) +) + +;; Packed fields with conflicting sets. +(module + + ;; CHECK: (type $struct (struct (field i16))) + + ;; CHECK: (type $1 (func)) + + ;; CHECK: (import "a" "b" (global $import i32)) + (import "a" "b" (global $import i32)) + + (type $struct (struct (field i16))) + + ;; CHECK: (func $test-struct (type $1) + ;; CHECK-NEXT: (local $x (ref null $struct)) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (global.get $import) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get_s $struct 0 + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get_u $struct 0 + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test-struct + (local $x (ref null $struct)) + (if + (global.get $import) + (then + (local.set $x + (struct.new $struct + (i32.const -1) + ) + ) + ) + (else + (local.set $x + (struct.new $struct + (i32.const 42) + ) + ) + ) + ) + ;; We cannot infer anything for these reads. + (drop + (struct.get_s $struct 0 + (local.get $x) + ) + ) + (drop + (struct.get_u $struct 0 + (local.get $x) + ) + ) + ) +) + +;; Packed fields with different sets that actually do not conflict. +(module + + ;; CHECK: (type $struct (struct (field i16))) + + ;; CHECK: (type $1 (func)) + + ;; CHECK: (import "a" "b" (global $import i32)) + (import "a" "b" (global $import i32)) + + (type $struct (struct (field i16))) + + ;; CHECK: (func $test-struct (type $1) + ;; CHECK-NEXT: (local $x (ref null $struct)) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (global.get $import) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 65535) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get_s $struct 0 + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get_u $struct 0 + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 65535) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test-struct + (local $x (ref null $struct)) + (if + (global.get $import) + (then + (local.set $x + (struct.new $struct + (i32.const -1) + ) + ) + ) + (else + (local.set $x + (struct.new $struct + (i32.const 65535) + ) + ) + ) + ) + ;; We can infer here because -1 and 65535 are actually the same, after + ;; truncation. + (drop + (struct.get_s $struct 0 + (local.get $x) + ) + ) + (drop + (struct.get_u $struct 0 + (local.get $x) + ) + ) + ) +) + ;; Test that we do not error on array.init of a bottom type. (module (type $"[mut:i32]" (array (mut i32))) |