From be904ae09b912a31805495b44801bc2418a3fe87 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 8 Sep 2025 14:28:35 -0700 Subject: [PATCH] [NFC] Add tests for missing exact CFP optimizations CFP takes advantage of exact type information, but it currently does so only for immutable fields. It is also unnecessarily conservative about how it propagates type information so that sets to a type inhibit optimizations of its sibling types, even though those sets cannot possibly affect the siblings. Add tests for these cases to demonstrate the benefit of follow-on PRs that will fix these issues. --- test/lit/passes/cfp.wast | 365 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 365 insertions(+) diff --git a/test/lit/passes/cfp.wast b/test/lit/passes/cfp.wast index 324178d331d..16e80c5c8f6 100644 --- a/test/lit/passes/cfp.wast +++ b/test/lit/passes/cfp.wast @@ -2499,6 +2499,371 @@ ) ) +(module + ;; Same as above but now the fields are mutable. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (sub (struct (field (mut i32))))) + (type $A (sub (struct (field (mut i32))))) + ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) + (type $B (sub $A (struct (field (mut 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: (struct.get $A 0 + ;; CHECK-NEXT: (local.get $A-exact) + ;; 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 should be able to optimize both exact references TODO. + (drop + (struct.get $A 0 + (local.get $A-exact) + ) + ) + (drop + (struct.get $B 0 + (local.get $B-exact) + ) + ) + ) +) + +(module + ;; Same as above but now we add no-op sets. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (sub (struct (field (mut i32))))) + (type $A (sub (struct (field (mut i32))))) + ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) + (type $B (sub $A (struct (field (mut 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: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A-exact) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B-exact) + ;; CHECK-NEXT: (i32.const 20) + ;; 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: (struct.get $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (local.get $A-exact) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $B 0 + ;; CHECK-NEXT: (local.get $B-exact) + ;; 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) + ) + ) + ) + ;; No-op exact sets should not inhibit optimization. + (struct.set $A 0 + (local.get $A-exact) + (i32.const 10) + ) + (struct.set $B 0 + (local.get $B-exact) + (i32.const 20) + ) + ;; We should be able to optimize an inexact $B, but not $A TODO. + (drop + (struct.get $A 0 + (local.get $A) + ) + ) + (drop + (struct.get $B 0 + (local.get $B) + ) + ) + ;; We should be able to optimize both exact references TODO. + (drop + (struct.get $A 0 + (local.get $A-exact) + ) + ) + (drop + (struct.get $B 0 + (local.get $B-exact) + ) + ) + ) +) + +(module + ;; Sets to a subtype should not affect exact gets of a supertype or sibling. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (sub (struct (field (mut i32))))) + (type $A (sub (struct (field (mut i32))))) + ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) + (type $B (sub $A (struct (field (mut i32))))) + ;; CHECK: (type $C (sub $A (struct (field (mut i32))))) + (type $C (sub $A (struct (field (mut i32))))) + ) + + ;; CHECK: (type $3 (func)) + + ;; CHECK: (type $4 (func (param (ref $B)))) + + ;; CHECK: (type $5 (func (param (ref $A) (ref $B) (ref $C)))) + + ;; CHECK: (type $6 (func (param (ref (exact $A)) (ref (exact $B)) (ref (exact $C))))) + + ;; CHECK: (func $news (type $3) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $B + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $C + ;; CHECK-NEXT: (i32.const 30) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $news + (drop + (struct.new $A + (i32.const 10) + ) + ) + (drop + (struct.new $B + (i32.const 20) + ) + ) + (drop + (struct.new $C + (i32.const 30) + ) + ) + ) + + ;; CHECK: (func $set-B (type $4) (param $B (ref $B)) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (i32.const 666) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $set-B (param $B (ref $B)) + ;; Inhibits optimizations on B and inexact A only. + (struct.set $B 0 + (local.get $B) + (i32.const 666) + ) + ) + + ;; CHECK: (func $inexact-gets (type $5) (param $A (ref $A)) (param $B (ref $B)) (param $C (ref $C)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $C 0 + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $inexact-gets (param $A (ref $A)) (param $B (ref $B)) (param $C (ref $C)) + (drop + (struct.get $A 0 + (local.get $A) + ) + ) + (drop + (struct.get $B 0 + (local.get $B) + ) + ) + ;; This should be optimizable TODO. + (drop + (struct.get $C 0 + (local.get $C) + ) + ) + ) + + ;; CHECK: (func $exact-gets (type $6) (param $A-exact (ref (exact $A))) (param $B-exact (ref (exact $B))) (param $C-exact (ref (exact $C))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (local.get $A-exact) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $B 0 + ;; CHECK-NEXT: (local.get $B-exact) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $C 0 + ;; CHECK-NEXT: (local.get $C-exact) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $exact-gets (param $A-exact (ref (exact $A))) + (param $B-exact (ref (exact $B))) + (param $C-exact (ref (exact $C))) + (drop + ;; This should be optimizable TODO. + (struct.get $A 0 + (local.get $A-exact) + ) + ) + ;; Not optimizable. + (drop + (struct.get $B 0 + (local.get $B-exact) + ) + ) + ;; This should be optimizable TODO. + (drop + (struct.get $C 0 + (local.get $C-exact) + ) + ) + ) +) + ;; A type with two subtypes. A copy on the parent can affect either child. (module (rec