Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GC] Ignore public types in GlobalTypeOptimization #7017

Merged
merged 2 commits into from
Oct 17, 2024

Conversation

kripken
Copy link
Member

@kripken kripken commented Oct 17, 2024

TypeUpdater which it uses internally already does so, but we must also ignore
such types earlier, and make no other modifications to them.

Helps #7015

@kripken kripken requested a review from tlively October 17, 2024 18:53
@kripken
Copy link
Member Author

kripken commented Oct 17, 2024

OTOH, SignaturePruning goes further than this:

// Rewrite the types. We pass in all the types we intend to modify as being
// "additional private types" because we have proven above that they are
// safe to modify, even if they are technically public (e.g. they may be in
// a singleton big rec group that is public because one member is public).

It checks if individual types are exported etc., and not just their entire rec group.

That is more optimal, obviously, so I guess GTO should do the same? Or has our thinking changed since then @tlively ?

@kripken
Copy link
Member Author

kripken commented Oct 17, 2024

The SignaturePruning code would need work, as it errors on this (adapated from the same original testcase):

;; The struct type is public, so we cannot modify it and replace
;; the reference to the function type with the pruned version.
(module
  (rec
    (type $none (func))
    (type $much (func (param i32)))

    (type $struct (struct (field (ref $much))))
  )

  (func $exported (export "exported") (type $none)
    ;; This makes the rec group public.
  )

  (func $unused-param (type $much) (param $param i32)
    ;; We can remove this param.
  )

  (func $caller
    ;; We can remove this param.
    (call $unused-param
      (i32.const 0)
    )
  )

  (func $struct.new
    ;; This struct.new causes the problem mentioned above.
    (drop
      (struct.new $struct
        (ref.func $unused-param)
      )
    )
  )
)

Copy link
Member

@tlively tlively left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could recover some of the lost optimization potential by changing the field of child3 to be (ref nofunc) and continuing to drop the values that would be used to initialize it.

@tlively
Copy link
Member

tlively commented Oct 17, 2024

The extra optimizations SignaturePruning does are only safe because they leave the public types unchanged where they appear in the module interface and create new, optimized copies of the types for internal use. At least, that's the idea. I'm not sure it entirely makes sense, though. What if one of the exported functions is also referenced internally, and the code with the unoptimized type conflicts with the code with the optimized type?

@kripken
Copy link
Member Author

kripken commented Oct 17, 2024

We could recover some of the lost optimization potential by changing the field of child3 to be (ref nofunc) and continuing to drop the values that would be used to initialize it.

Sorry, I don't follow - what values would those be? That's an uninhabitable type so there is nothing we can use. I'm not sure how we can fix up this code, in other words:

      (struct.new $child3
        (ref.func $func)
      )

It is not unreachable.

@tlively
Copy link
Member

tlively commented Oct 17, 2024

Oh, right, that wouldn't work because we need an inhabitable type. (ref null nofunc) would work if the original field were nullable, but otherwise I guess there's not much we can do.

@tlively
Copy link
Member

tlively commented Oct 17, 2024

I think we could do the same thing SignaturePruning does and create optimized copies of public struct types, but we could only do that safely in cases where the struct type is only public due to its rec group and not because it can actually flow out of the module. Right now we have no way to differentiate those different degrees of visibility.

@kripken
Copy link
Member Author

kripken commented Oct 17, 2024

@tlively

I think we could do the same thing SignaturePruning does and create optimized copies of public struct types, but we could only do that safely in cases where the struct type is only public due to its rec group and not because it can actually flow out of the module. Right now we have no way to differentiate those different degrees of visibility.

Yeah, that's exactly the issue. We'd need to identify those degrees, which means a very careful analysis of why something is public. I don't see a good way to do that atm, so I'll open a PR for SignaturePruning to limit it as well.

The good news is that at least on Java output I don't see any harm to limiting GTO and SignaturePruning in this way. But in theory on output like in #7015 (from Kotlin apparently) there is a single big rec group with mixed public/private contents that we could in theory partially optimize. But right now we break on them, so fixing things is a set up for them too.

@kripken kripken merged commit 245d915 into WebAssembly:main Oct 17, 2024
13 checks passed
@kripken kripken deleted the gto.visibility branch October 17, 2024 21:27
@kripken
Copy link
Member Author

kripken commented Oct 17, 2024

Perhaps we can think of a way to split up public rec groups in closed world mode in a safe way, to make parts of such a group private again?

@tlively
Copy link
Member

tlively commented Oct 17, 2024

Perhaps we can think of a way to split up public rec groups in closed world mode in a safe way, to make parts of such a group private again?

See my comment here: #7015 (comment)

@tlively
Copy link
Member

tlively commented Oct 17, 2024

Yeah, that's exactly the issue. We'd need to identify those degrees, which means a very careful analysis of why something is public. I don't see a good way to do that atm, so I'll open a PR for SignaturePruning to limit it as well.

In principle we could do this by finding all the public types without considering rec group peers and giving them a different classification. But it would be better just to fix #6965 and let producers give us reasonable modules that don't have these issues to begin with.

@kripken
Copy link
Member Author

kripken commented Oct 17, 2024

Yeah, good point, #6965 would fix this too by having public annotations on types. That's better than on rec groups.

kripken added a commit that referenced this pull request Oct 18, 2024
kripken added a commit that referenced this pull request Oct 18, 2024
Similar to #7017 . As with that PR, this reduces some optimizations that were
valid, as we tried to do something complex here and refine types in a public
rec group when it seemed safe to do so, but our analysis was incomplete.
The testcase here shows how another operation can end up causing a
dependency that breaks things, if another type that uses one that we
modify is public. To be safe, ignore all public types. In the future perhaps we
can find a good way to handle "almost-private" types in public rec groups,
in closed world.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants