Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 14 additions & 9 deletions src/passes/ConstantFieldPropagation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,15 @@ struct FunctionOptimizer : public WalkerPass<PostWalker<FunctionOptimizer>> {
return heapType;
}

PossibleConstantValues getInfo(HeapType type, Index index) {
if (auto it = propagatedInfos.find(type); it != propagatedInfos.end()) {
PossibleConstantValues getInfo(HeapType type, Index index, Exactness exact) {
// If the reference is exact and the field immutable, then we are reading
// exactly what was written to struct.news and nothing else.
auto mutable_ = index == StructUtils::DescriptorIndex
? Immutable
: GCTypeUtils::getField(type, index)->mutable_;
auto& infos =
(exact == Inexact || mutable_ == Mutable) ? propagatedInfos : rawNewInfos;
if (auto it = infos.find(type); it != infos.end()) {
// There is information on this type, fetch it.
return it->second[index];
}
Expand Down Expand Up @@ -177,7 +184,8 @@ struct FunctionOptimizer : public WalkerPass<PostWalker<FunctionOptimizer>> {
// Find the info for this field, and see if we can optimize. First, see if
// there is any information for this heap type at all. If there isn't, it is
// as if nothing was ever noted for that field.
PossibleConstantValues info = getInfo(heapType, index);
PossibleConstantValues info =
getInfo(heapType, index, ref->type.getExactness());
if (!info.hasNoted()) {
// This field is never written at all. That means that we do not even
// construct any data of this type, and so it is a logic error to reach
Expand Down Expand Up @@ -499,12 +507,9 @@ struct ConstantFieldPropagation : public Pass {
// Prepare data we will need later.
SubTypes subTypes(*module);

PCVStructValuesMap rawNewInfos;
if (refTest) {
// The refTest optimizations require the raw new infos (see above), but we
// can skip copying here if we'll never read this.
rawNewInfos = combinedNewInfos;
}
// Copy the unpropagated data before we propagate. We use this in precise
// lookups.
auto rawNewInfos = combinedNewInfos;

// Handle subtyping. |combinedInfo| so far contains data that represents
// each struct.new and struct.set's operation on the struct type used in
Expand Down
80 changes: 80 additions & 0 deletions test/lit/passes/cfp-reftest-desc.wast
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,83 @@
)
)

(module
(rec
;; CHECK: (rec
;; CHECK-NEXT: (type $super (sub (descriptor $super.desc (struct))))
(type $super (sub (descriptor $super.desc (struct))))
;; CHECK: (type $super.desc (sub (describes $super (struct))))
(type $super.desc (sub (describes $super (struct))))

;; CHECK: (type $func (func (param i32) (result i32)))
(type $func (func (param i32) (result i32)))

;; CHECK: (type $sub (sub $super (descriptor $sub.desc (struct))))
(type $sub (sub $super (descriptor $sub.desc (struct))))
;; CHECK: (type $sub.desc (sub $super.desc (describes $sub (struct))))
(type $sub.desc (sub $super.desc (describes $sub (struct))))
)

;; CHECK: (type $5 (func (result (ref (exact $super.desc)))))

;; CHECK: (global $A (ref (exact $super.desc)) (struct.new_default $super.desc))
(global $A (ref (exact $super.desc)) (struct.new $super.desc))

;; CHECK: (global $B (ref (exact $sub.desc)) (struct.new_default $sub.desc))
(global $B (ref (exact $sub.desc)) (struct.new $sub.desc))

;; CHECK: (func $test (type $5) (result (ref (exact $super.desc)))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.new_default $super
;; CHECK-NEXT: (global.get $A)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.new_default $sub
;; CHECK-NEXT: (global.get $B)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (block (result (ref (exact $super.desc)))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.as_non_null
;; CHECK-NEXT: (block (result nullref)
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (global.get $A)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test (result (ref (exact $super.desc)))
(drop
(struct.new_default $super
(global.get $A)
)
)
(drop
(struct.new_default $sub
(global.get $B)
)
)
;; We read from an exact $super here, so the type of the ref.get_desc is
;; exact as well. If we ignore that in the optimization, we might think that
;; the two struct.news before us are two possible values, one from $super and
;; one from $sub, and if we emitted a ref.test between those values, we'd get
;; a non-exact value that does not validate.
;;
;; Instead, we should look only at $super itself, and optimize to $A.
(ref.get_desc $super
(block (result (ref null (exact $super)))
(ref.null $super)
)
)
)

;; CHECK: (func $func (type $func) (param $0 i32) (result i32)
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
(func $func (type $func) (param $0 i32) (result i32)
(i32.const 42)
)
)

55 changes: 55 additions & 0 deletions test/lit/passes/cfp-reftest.wast
Original file line number Diff line number Diff line change
Expand Up @@ -1456,3 +1456,58 @@
)
)
)

(module
;; CHECK: (type $struct (sub (struct (field i32))))
(type $struct (sub (struct i32)))
;; CHECK: (type $1 (func))

;; CHECK: (type $substruct (sub $struct (struct (field i32) (field f64))))
(type $substruct (sub $struct (struct i32 f64)))

;; CHECK: (type $3 (func (param (ref null (exact $struct))) (result i32)))

;; CHECK: (func $create (type $1)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.new $substruct
;; CHECK-NEXT: (i32.const 20)
;; CHECK-NEXT: (f64.const 3.14159)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $create
;; Used below.
(drop
(struct.new $struct
(i32.const 10)
)
)
(drop
(struct.new $substruct
(i32.const 20)
(f64.const 3.14159)
)
)
)
;; CHECK: (func $get (type $3) (param $struct (ref null (exact $struct))) (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.as_non_null
;; CHECK-NEXT: (local.get $struct)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
(func $get (param $struct (ref null (exact $struct))) (result i32)
;; The type here is exact, so we do not even need to do a select: only the
;; super's value is possible, 10.
(struct.get $struct 0
(local.get $struct)
)
)
)

154 changes: 154 additions & 0 deletions test/lit/passes/cfp.wast
Original file line number Diff line number Diff line change
Expand Up @@ -2389,6 +2389,116 @@
)
)

(module
(rec
;; CHECK: (rec
;; CHECK-NEXT: (type $A (sub (struct (field i32))))
(type $A (sub (struct (field i32))))
;; CHECK: (type $B (sub $A (struct (field i32))))
(type $B (sub $A (struct (field i32))))
)

;; CHECK: (type $2 (func (param i32)))

;; CHECK: (func $test (type $2) (param $0 i32)
;; CHECK-NEXT: (local $A (ref $A))
;; CHECK-NEXT: (local $B (ref $B))
;; CHECK-NEXT: (local $A-exact (ref (exact $A)))
;; CHECK-NEXT: (local $B-exact (ref (exact $B)))
;; CHECK-NEXT: (local.set $A
;; CHECK-NEXT: (local.tee $A-exact
;; CHECK-NEXT: (struct.new $A
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $B
;; CHECK-NEXT: (local.tee $B-exact
;; CHECK-NEXT: (struct.new $B
;; CHECK-NEXT: (i32.const 20)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $A 0
;; CHECK-NEXT: (local.get $A)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.as_non_null
;; CHECK-NEXT: (local.get $B)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 20)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.as_non_null
;; CHECK-NEXT: (local.get $A-exact)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.as_non_null
;; CHECK-NEXT: (local.get $B-exact)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 20)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test (param $0 i32)
(local $A (ref $A))
(local $B (ref $B))
(local $A-exact (ref (exact $A)))
(local $B-exact (ref (exact $B)))
(local.set $A
(local.tee $A-exact
(struct.new $A
(i32.const 10)
)
)
)
(local.set $B
(local.tee $B-exact
(struct.new $B
(i32.const 20)
)
)
)
;; We can optimize an inexact $B, but not $A.
(drop
(struct.get $A 0
(local.get $A)
)
)
(drop
(struct.get $B 0
(local.get $B)
)
)
;; We can optimize both exact references.
(drop
(struct.get $A 0
(local.get $A-exact)
)
)
(drop
(struct.get $B 0
(local.get $B-exact)
)
)
)
)

;; A type with two subtypes. A copy on the parent can affect either child.
(module
(rec
Expand Down Expand Up @@ -2947,3 +3057,47 @@
)
)
)

(module
;; CHECK: (type $A (struct (field (mut i32))))
(type $A (struct (field (mut i32))))

;; CHECK: (type $1 (func))

;; CHECK: (func $test (type $1)
;; CHECK-NEXT: (local $A.exact (ref (exact $A)))
;; CHECK-NEXT: (local.set $A.exact
;; CHECK-NEXT: (struct.new $A
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (struct.set $A 0
;; CHECK-NEXT: (local.get $A.exact)
;; CHECK-NEXT: (i32.const 20)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $A 0
;; CHECK-NEXT: (local.get $A.exact)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
(local $A.exact (ref (exact $A)))
(local.set $A.exact
(struct.new $A
(i32.const 10)
)
)
;; This set prevents us from optimizing. We should not be confused by the
;; exactness of the ref.
(struct.set $A 0
(local.get $A.exact)
(i32.const 20)
)
(drop
(struct.get $A 0
(local.get $A.exact)
)
)
)
)
Loading