Sfoglia il codice sorgente

SemIR fidelity when representing rewrite semantics (#3833)

The toolchain's [Semantic IR][semir] should start off modeling the full,
complex, and rich library-based and generic extension point semantics of
Carbon
without eliding any layers or rewrites for compile time efficiency. We
shouldn't
front-load elision or optimization when implementing the designs.

Once we have a full-fidelity implementation, we should work to build an
efficient elision, short-circuit, or common case simplification into the
design
itself sufficient to make the SemIR model efficient. Only if we cannot
find a
reasonable approach for that should we diverge the SemIR model to
optimize its
efficiency, and we should preserve full fidelity in an optional mode.

[semir]:

https://docs.google.com/document/d/1RRYMm42osyqhI2LyjrjockYCutQ5dOf8Abu50kTrkX0/edit?resourcekey=0-kHyqOESbOHmzZphUbtLrTw#heading=h.503m6lfcnmui

---------

Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com>
Co-authored-by: Richard Smith <richard@metafoo.co.uk>
Co-authored-by: Carbon Infra Bot <carbon-external-infra@google.com>
Chandler Carruth 1 anno fa
parent
commit
31060ca38a
1 ha cambiato i file con 231 aggiunte e 0 eliminazioni
  1. 231 0
      proposals/p3833.md

+ 231 - 0
proposals/p3833.md

@@ -0,0 +1,231 @@
+# SemIR fidelity when representing rewrite semantics
+
+<!--
+Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+Exceptions. See /LICENSE for license information.
+SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+-->
+
+[Pull request](https://github.com/carbon-language/carbon-lang/pull/3833)
+
+<!-- toc -->
+
+## Table of contents
+
+-   [Abstract](#abstract)
+-   [Problem](#problem)
+-   [Background](#background)
+-   [Proposal](#proposal)
+    -   [Work to incorporate needed simplifications directly into the design](#work-to-incorporate-needed-simplifications-directly-into-the-design)
+    -   [Keep a full fidelity mode for any other optimizations](#keep-a-full-fidelity-mode-for-any-other-optimizations)
+    -   [Example: overloaded operators](#example-overloaded-operators)
+-   [Rationale](#rationale)
+-   [Alternatives considered](#alternatives-considered)
+    -   [Strictly adhere to a full-fidelity model](#strictly-adhere-to-a-full-fidelity-model)
+    -   [Directly implement the optimized model](#directly-implement-the-optimized-model)
+
+<!-- tocstop -->
+
+## Abstract
+
+The toolchain's [Semantic IR][semir] should start off modeling the full,
+complex, and rich library-based and generic extension point semantics of Carbon
+without eliding any layers or rewrites for compile time efficiency. We shouldn't
+front-load elision or optimization when implementing the designs.
+
+Once we have a full-fidelity implementation, we should work to build an
+efficient elision, short-circuit, or common case simplification into the design
+itself sufficient to make the SemIR model efficient. Only if we cannot find a
+reasonable approach for that should we diverge the SemIR model to optimize its
+efficiency, and we should preserve full fidelity in an optional mode.
+
+[semir]:
+    https://docs.google.com/document/d/1RRYMm42osyqhI2LyjrjockYCutQ5dOf8Abu50kTrkX0/edit?resourcekey=0-kHyqOESbOHmzZphUbtLrTw#heading=h.503m6lfcnmui
+
+## Problem
+
+Carbon's design heavily leverages types and APIs defined in the `Core` package
+and implicitly imported with the prelude. It also defines rich extension points
+and generic semantic models for the language, typically through rewrites from
+the user-facing syntax into more complex syntax that operates through
+`interface`s and `impl`s that provide the generic model and customization
+points.
+
+Modeling this at full fidelity in the toolchain's [Semantic IR][semir] is
+expected to create an unreasonably expensive representation for common and
+pervasive patterns in code. On the other hand, every divergence between the
+SemIR model and the design adds both risk and cost. When we can keep the two in
+sync, we get the best engineering tradeoffs, provided it is reasonably
+efficient.
+
+## Background
+
+Several proposals have started to surface these kinds of challenge, but to a
+lesser degree. Some examples:
+
+-   [Proposal #820 - Implicit conversions](https://github.com/carbon-language/carbon-lang/pull/820)
+-   [Proposal #845 - `as` expressions](https://github.com/carbon-language/carbon-lang/pull/845)
+-   [Proposal #1083 - Arithmetic expressions](https://github.com/carbon-language/carbon-lang/pull/1083)
+
+The most significant step that motivates capturing an explicit strategy here is
+[proposal #3720 - Binding operators](https://github.com/carbon-language/carbon-lang/pull/3720).
+
+## Proposal
+
+Initially, the toolchain should have SemIR model the full semantics of Carbon,
+with all rewrites, expressive hooks, and library references. This will serve two
+purposes as we build out the toolchain's support for all of Carbon's features.
+
+### Work to incorporate needed simplifications directly into the design
+
+First, this will allow us to study the resulting SemIR, and understand exactly
+how and where the efficiency explodes for expected coding patterns. We should
+use this to try to adjust the design to build in targeted common-case
+simplifications and elisions that make precise modeling of the design
+sufficiently cheap in practice.
+
+We see this pattern emerging in good ways already today in the toolchain with
+implicit conversions: when calling functions, we need to allow for implicit
+conversions. But we don't want to have an identity implicit conversion on all
+the arguments which already are the correct type. Rather than notionally
+"always" implicitly converting, we make the design itself specify that the
+implicit conversion only occurs when needed and can then have the desired
+minimal representation in SemIR.
+
+Beyond simplifying the SemIR model, it also ends up addressing an important
+aspect of the semantics -- we don't want to create unnecessary temporaries to
+initialize rather than directly initializing. The toolchain in fact follows
+similar patterns to model expression categories for similar reasons -- the
+minimal semantic model is also the desired design.
+
+We can even incorporate other kinds of design changes to allow the full fidelity
+SemIR model to be efficient, such as assuring that we can build and reuse a
+cached representation repeatedly and cheaply rather than requiring it to be
+rebuilt on each instance of a specific pattern.
+
+### Keep a full fidelity mode for any other optimizations
+
+We may still ultimately end up unable to achieve the desired SemIR efficiency
+even after making all the simplifications we can to the design itself. If and
+when we reach this point, we will still want to have the full fidelity
+implementation and be able to use it behind some option. We can use it to
+understand how the design works in complex cases and to debug unexpected
+behavior in the toolchain. This means starting with that implementation doesn't
+result in sunk cost.
+
+### Example: overloaded operators
+
+> Note: this example isn't trying to capture all the nuance of the current
+> design or suggest any change to the design. It is merely trying to provide an
+> illustration of how we might follow the proposal in practice.
+
+As an example to help illustrate this, how should the toolchain model overloaded
+operators? This proposal suggests that _initially_, the implementation should
+aim to fully model the rewrite-based dispatch through interfaces in the prelude.
+That is, each use of `x + y` should turn into roughly equivalent SemIR as would
+be used to model the rewritten semantics of
+`x.(package#Core.AddWith(typeof(y)).Op)(y)`. This in turn would dispatch to an
+exact-type implementation which would provide any implicit conversions, and so
+on. And importantly, this would occur even when we know that the types of `x`
+and `y` are exactly `i32` and we could alternatively directly hard code the
+toolchain to use the desired builtin implementation.
+
+To be precise, the expectation is that the SemIR for `x + y` should as a
+consequence model all of:
+
+-   Looking up the `package#Core.AddWith` interface, using the imagined syntax
+    `package#<Name>` for directly looking up a specific package name (as opposed
+    to doing unqualified name lookup).
+-   Passing the type of `y` as a parameter to the interface, which is
+    illustrated with the imagined syntax `typeof(<expression>)` but would likely
+    directly use the type rather than forming an actual `typeof` expression (see
+    below).
+-   Looking up the `Op` method.
+-   Calling that `Op` method on `x` with `y` as its parameter just as we would
+    model any call to `object.(Interface.Method)(y)`.
+
+> Note: this isn't a proposal for either the `package#<Name>` or
+> `typeof(<expression>)` imagined syntaxes. If we want these syntaxes, that will
+> be a separate proposal.
+
+However, specific things that would still be reasonable without deviating from
+the full semantic modeling here:
+
+-   Not representing a user-written `typeof` style expression to compute the
+    type, or duplicating the `y` expression.
+-   Caching and re-using SemIR for immutable things like successfully resolved
+    name lookup. Concretely, re-using a single cached lookup of
+    `package#Core.AddWith` would still provide the full model. And similarly,
+    re-using an already computed parameterization such as
+    `package#Core.AddWith(i32)` repeatedly, and the lookup of `Op` within that
+    parameterization.
+
+The reason is that these don't change the semantics in any way -- they are
+constructively the same results.
+
+This should be the initial implementation _goal_, not a requirement for any
+incremental progress on implementation. Short-circuiting or other approximations
+used in incrementally building towards this goal should always be reasonable to
+consider based on the tradeoffs at hand. But they should be seen as temporary,
+incremental measures and where possible removed so we can evaluate the full
+semantic model when the implementation is complete.
+
+Based on the practical costs that this kind of model incurs once we have it
+implemented, we should first look for ways to adjust the design itself to avoid
+forming the complex semantic expansion in enough cases to make the model
+scalable. And only when we've exhausted those options begin adding long-term
+shortcuts in the SemIR model for common cases with a option to disable them for
+testing and debugging.
+
+## Rationale
+
+-   [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write)
+    -   It's easy when specifying the design of the language with rewrite
+        semantics in a way that accidentally has a cycle -- where the rewrite
+        result is still a construct that should be rewritten. Without the
+        proposed constraint, these accidental cycles are easily resolved in the
+        implementation by exiting at a convenient place. However, this proposal
+        will ensure that the rule for where the cycle is broken is actually
+        surfaced into the design, making the design more robust and complete in
+        describing the expected behavior.
+-   [Language tools and ecosystem](/docs/project/goals.md#language-tools-and-ecosystem)
+    -   Preserving a full fidelity model of the language design in SemIR helps
+        ensure we can build rich and powerful tools that reason about even
+        complex or typically "hidden" parts of Carbon's design. This has
+        regularly proven critical in the design of effective tooling for C++ and
+        other languages.
+-   [Fast and scalable development](/docs/project/goals.md#fast-and-scalable-development)
+    -   We can't afford for full fidelity representation of Carbon's design to
+        make the practical use of the toolchain have poor compile times and slow
+        down the development experience with Carbon. We need to have a strategy
+        for addressing the expected overhead of the level of customization and
+        layered design that we're building into Carbon.
+
+## Alternatives considered
+
+### Strictly adhere to a full-fidelity model
+
+We could simply insist on never deviating from the full-fidelity model. However,
+this would force us to compromise at least one of our top-level goals for
+Carbon: either we would have to regress the speed and scalability of
+development, or lose some of the simplicity and cohesive design.
+
+Both of those seem worse outcomes than having some limited divergence between
+SemIR and the design, and there exist reasonable mitigations such as a option to
+switch between modes.
+
+### Directly implement the optimized model
+
+We could directly jump to an optimized model that we expect to match the common
+case behavior, and add opt-in complexity when needed for non-common-cases.
+However, this would both remove a useful tool in driving simplification of the
+design itself that can result in a overall superior tradeoff. It would also
+create risk of divergence, and the need to eventually implement mode to disable
+the optimization.
+
+In some cases, we would end up discovering a need to simplify the design for
+semantic reasons, and the divergence would have resulted in wasted effort in the
+toolchain.
+
+Starting with the full fidelity model seems to more reliably converge the design
+early and result in a minimal set of effective optimizations.