From a1be2a8a38ca0493b000143f7b9999c25d64ff6e Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Tue, 21 Jun 2022 08:48:48 -0700 Subject: [PATCH] Track the arguments and witnesses on values indirectly. (#1335) This avoids the need to make copies of potentially large maps when the same bindings are used in multiple values, such as when forming a bound method value. --- explorer/ast/BUILD | 10 +++ explorer/ast/bindings.cpp | 14 ++++ explorer/ast/bindings.h | 57 +++++++++++++ explorer/ast/expression.h | 6 +- explorer/interpreter/BUILD | 1 + explorer/interpreter/interpreter.cpp | 63 +++++++++----- explorer/interpreter/type_checker.cpp | 29 ++++--- explorer/interpreter/value.cpp | 15 ++-- explorer/interpreter/value.h | 116 ++++++++++++-------------- 9 files changed, 203 insertions(+), 108 deletions(-) create mode 100644 explorer/ast/bindings.cpp create mode 100644 explorer/ast/bindings.h diff --git a/explorer/ast/BUILD b/explorer/ast/BUILD index 6d5651fe2d6e..674ab5f60259 100644 --- a/explorer/ast/BUILD +++ b/explorer/ast/BUILD @@ -89,6 +89,15 @@ cc_library( ], ) +cc_library( + name = "bindings", + srcs = ["bindings.cpp"], + hdrs = ["bindings.h"], + deps = [ + "//explorer/common:nonnull", + ], +) + cc_library( name = "declaration", srcs = ["declaration.cpp"], @@ -153,6 +162,7 @@ cc_library( hdrs = ["expression.h", "pattern.h"], deps = [ ":ast_node", + ":bindings", ":member", ":paren_contents", ":static_scope", diff --git a/explorer/ast/bindings.cpp b/explorer/ast/bindings.cpp new file mode 100644 index 000000000000..2458331e7a4d --- /dev/null +++ b/explorer/ast/bindings.cpp @@ -0,0 +1,14 @@ +// 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 + +#include "explorer/ast/bindings.h" + +namespace Carbon { + +auto Bindings::None() -> Nonnull { + static Nonnull bindings = new Bindings({}, {}); + return bindings; +} + +} // namespace Carbon diff --git a/explorer/ast/bindings.h b/explorer/ast/bindings.h new file mode 100644 index 000000000000..ee3c9afa4f46 --- /dev/null +++ b/explorer/ast/bindings.h @@ -0,0 +1,57 @@ +// 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 + +#ifndef CARBON_EXPLORER_AST_BINDINGS_H_ +#define CARBON_EXPLORER_AST_BINDINGS_H_ + +#include + +#include "explorer/common/nonnull.h" + +namespace Carbon { + +class ImplBinding; +class GenericBinding; +class Value; + +using BindingMap = + std::map, Nonnull>; +using ImplWitnessMap = + std::map, Nonnull>; + +// A set of evaluated bindings in some context, such as a function or class. +// +// These are shared by a context and all unparameterized entities within that +// context. For example, a class and the name of a method within that class +// will have the same set of bindings. +class Bindings { + public: + // Create an instantiated set of bindings for use during evaluation, + // containing both arguments and witnesses. + Bindings(BindingMap args, ImplWitnessMap witnesses) + : args_(args), witnesses_(witnesses) {} + + enum NoWitnessesTag { NoWitnesses }; + + // Create a set of bindings for use during type-checking, containing only the + // arguments but not the corresponding witnesses. + Bindings(BindingMap args, NoWitnessesTag) : args_(args), witnesses_() {} + + // Argument values corresponding to generic bindings. + auto args() const -> const BindingMap& { return args_; } + + // Witnesses corresponding to impl bindings. + auto witnesses() const -> const ImplWitnessMap& { return witnesses_; } + + // An empty set of bindings. + static auto None() -> Nonnull; + + private: + BindingMap args_; + ImplWitnessMap witnesses_; +}; + +} // namespace Carbon + +#endif // CARBON_EXPLORER_AST_BINDINGS_H_ diff --git a/explorer/ast/expression.h b/explorer/ast/expression.h index e7f83131f688..5f883a49c852 100644 --- a/explorer/ast/expression.h +++ b/explorer/ast/expression.h @@ -13,6 +13,7 @@ #include "common/ostream.h" #include "explorer/ast/ast_node.h" +#include "explorer/ast/bindings.h" #include "explorer/ast/member.h" #include "explorer/ast/paren_contents.h" #include "explorer/ast/static_scope.h" @@ -510,11 +511,6 @@ class PrimitiveOperatorExpression : public Expression { std::vector> arguments_; }; -class GenericBinding; - -using BindingMap = - std::map, Nonnull>; - using ImplExpMap = std::map, Nonnull>; class CallExpression : public Expression { diff --git a/explorer/interpreter/BUILD b/explorer/interpreter/BUILD index f7f285ac1f14..5ef001199969 100644 --- a/explorer/interpreter/BUILD +++ b/explorer/interpreter/BUILD @@ -24,6 +24,7 @@ cc_library( ":stack", "//common:check", "//common:ostream", + "//explorer/ast:bindings", "//explorer/ast:declaration", "//explorer/ast:expression", "//explorer/ast:member", diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index 0b39af034154..23777e215899 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -109,10 +109,16 @@ class Interpreter { auto InstantiateType(Nonnull type, SourceLocation source_loc) -> ErrorOr>; + // Instantiate a set of bindings by replacing all type variables that occur + // within it by the current values of those variables. + auto InstantiateBindings(Nonnull bindings, + SourceLocation source_loc) + -> ErrorOr>; + // Call the function `fun` with the given `arg` and the `witnesses` // for the function's impl bindings. auto CallFunction(const CallExpression& call, Nonnull fun, - Nonnull arg, const ImplWitnessMap& witnesses) + Nonnull arg, ImplWitnessMap&& witnesses) -> ErrorOr; void PrintState(llvm::raw_ostream& out); @@ -464,26 +470,38 @@ auto Interpreter::InstantiateType(Nonnull type, } case Value::Kind::NominalClassType: { const auto& class_type = cast(*type); - BindingMap inst_type_args; - for (const auto& [ty_var, ty_arg] : class_type.type_args()) { - CARBON_ASSIGN_OR_RETURN(inst_type_args[ty_var], - InstantiateType(ty_arg, source_loc)); - } - ImplWitnessMap witnesses = class_type.witnesses(); - for (auto& [bind, witness] : witnesses) { - if (auto* sym = dyn_cast(witness)) { - CARBON_ASSIGN_OR_RETURN(witness, - EvalExpRecursively(&sym->impl_expression())); - } - } - return arena_->New(&class_type.declaration(), - inst_type_args, witnesses); + CARBON_ASSIGN_OR_RETURN( + Nonnull bindings, + InstantiateBindings(&class_type.bindings(), source_loc)); + return arena_->New(&class_type.declaration(), bindings); } default: return type; } } +auto Interpreter::InstantiateBindings(Nonnull bindings, + SourceLocation source_loc) + -> ErrorOr> { + BindingMap args = bindings->args(); + for (auto& [var, arg] : args) { + CARBON_ASSIGN_OR_RETURN(arg, InstantiateType(arg, source_loc)); + } + + ImplWitnessMap witnesses = bindings->witnesses(); + for (auto& [bind, witness] : witnesses) { + if (auto* sym = dyn_cast(witness)) { + CARBON_ASSIGN_OR_RETURN(witness, + EvalExpRecursively(&sym->impl_expression())); + } + } + + if (args == bindings->args() && witnesses == bindings->witnesses()) { + return bindings; + } + return arena_->New(std::move(args), std::move(witnesses)); +} + auto Interpreter::Convert(Nonnull value, Nonnull destination_type, SourceLocation source_loc) @@ -609,8 +627,7 @@ auto Interpreter::Convert(Nonnull value, auto Interpreter::CallFunction(const CallExpression& call, Nonnull fun, Nonnull arg, - const ImplWitnessMap& witnesses) - -> ErrorOr { + ImplWitnessMap&& witnesses) -> ErrorOr { if (trace_stream_) { **trace_stream_ << "calling function: " << *fun << "\n"; } @@ -703,13 +720,15 @@ auto Interpreter::CallFunction(const CallExpression& call, CARBON_CHECK(PatternMatch(&name.params().value(), arg, call.source_loc(), ¶ms_scope, generic_args, trace_stream_, this->arena_)); + Nonnull bindings = + arena_->New(std::move(generic_args), std::move(witnesses)); switch (decl.kind()) { case DeclarationKind::ClassDeclaration: return todo_.FinishAction(arena_->New( - &cast(decl), generic_args, witnesses)); + &cast(decl), bindings)); case DeclarationKind::InterfaceDeclaration: return todo_.FinishAction(arena_->New( - &cast(decl), generic_args, witnesses)); + &cast(decl), bindings)); default: CARBON_FATAL() << "unknown kind of ParameterizedEntityName " << decl; } @@ -751,7 +770,9 @@ auto Interpreter::StepExp() -> ErrorOr { ++i; } return todo_.FinishAction(arena_->New( - &generic_witness->declaration(), inst_impl.type_args(), witnesses)); + &generic_witness->declaration(), + arena_->New(inst_impl.type_args(), + std::move(witnesses)))); } } case ExpressionKind::IndexExpression: { @@ -1000,7 +1021,7 @@ auto Interpreter::StepExp() -> ErrorOr { } } return CallFunction(call, act.results()[0], act.results()[1], - witnesses); + std::move(witnesses)); } else if (act.pos() == 3 + int(num_impls)) { if (act.results().size() < 3 + num_impls) { // Control fell through without explicit return. diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index 84821da9a849..dc2791560aad 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -448,14 +448,17 @@ auto TypeChecker::GetBuiltinInterfaceType(SourceLocation source_loc, if (has_parameters != has_arguments) { return bad_builtin(); } - BindingMap bindings; + BindingMap binding_args; if (has_arguments) { TupleValue args(interface.arguments); if (!PatternMatch(&iface_decl->params().value()->value(), &args, source_loc, - std::nullopt, bindings, trace_stream_, this->arena_)) { + std::nullopt, binding_args, trace_stream_, + this->arena_)) { return bad_builtin(); } } + Nonnull bindings = + arena_->New(std::move(binding_args), Bindings::NoWitnesses); return arena_->New(iface_decl, bindings); } @@ -875,12 +878,13 @@ class ConstraintTypeBuilder { auto TypeChecker::Substitute( const std::map, Nonnull>& dict, Nonnull type) const -> Nonnull { - auto SubstituteIntoBindingMap = [&](const BindingMap& map) -> BindingMap { + auto SubstituteIntoBindings = + [&](const Bindings& bindings) -> Nonnull { BindingMap result; - for (const auto& [name, value] : map) { + for (const auto& [name, value] : bindings.args()) { result[name] = Substitute(dict, value); } - return result; + return arena_->New(std::move(result), Bindings::NoWitnesses); }; switch (type->kind()) { @@ -971,14 +975,14 @@ auto TypeChecker::Substitute( Nonnull new_class_type = arena_->New( &class_type.declaration(), - SubstituteIntoBindingMap(class_type.type_args())); + SubstituteIntoBindings(class_type.bindings())); return new_class_type; } case Value::Kind::InterfaceType: { const auto& iface_type = cast(*type); Nonnull new_iface_type = arena_->New( &iface_type.declaration(), - SubstituteIntoBindingMap(iface_type.args())); + SubstituteIntoBindings(iface_type.bindings())); return new_iface_type; } case Value::Kind::ConstraintType: { @@ -1975,13 +1979,15 @@ auto TypeChecker::TypeCheckExp(Nonnull e, CARBON_RETURN_IF_ERROR(DeduceCallBindings( call, ¶m_name.params().static_type(), generic_parameters, /*deduced_bindings=*/llvm::None, impl_bindings, impl_scope)); + Nonnull bindings = + arena_->New(call.deduced_args(), Bindings::NoWitnesses); const Declaration& decl = param_name.declaration(); switch (decl.kind()) { case DeclarationKind::ClassDeclaration: { Nonnull inst_class_type = arena_->New(&cast(decl), - call.deduced_args()); + bindings); call.set_static_type( arena_->New(inst_class_type)); call.set_value_category(ValueCategory::Let); @@ -1990,7 +1996,7 @@ auto TypeChecker::TypeCheckExp(Nonnull e, case DeclarationKind::InterfaceDeclaration: { Nonnull inst_iface_type = arena_->New(&cast(decl), - call.deduced_args()); + bindings); call.set_static_type( arena_->New(inst_iface_type)); call.set_value_category(ValueCategory::Let); @@ -2892,8 +2898,9 @@ auto TypeChecker::DeclareClassDeclaration(Nonnull class_decl, // above and/or by any enclosing generic classes. generic_args[binding] = *binding->symbolic_identity(); } - Nonnull self_type = - arena_->New(class_decl, generic_args); + Nonnull self_type = arena_->New( + class_decl, + arena_->New(std::move(generic_args), Bindings::NoWitnesses)); SetConstantValue(self, self_type); self->set_static_type(arena_->New(self_type)); diff --git a/explorer/interpreter/value.cpp b/explorer/interpreter/value.cpp index c1f1c33d8d32..7f7c37aa7145 100644 --- a/explorer/interpreter/value.cpp +++ b/explorer/interpreter/value.cpp @@ -46,14 +46,12 @@ static auto GetMember(Nonnull arena, Nonnull v, const auto& fun_decl = cast(**mem_decl); if (fun_decl.is_method()) { return arena->New(&fun_decl, v, - impl_witness->type_args(), - impl_witness->witnesses()); + &impl_witness->bindings()); } else { // Class function. auto* fun = cast(*fun_decl.constant_value()); return arena->New(&fun->declaration(), - impl_witness->type_args(), - impl_witness->witnesses()); + &impl_witness->bindings()); } } else { return CompilationError(source_loc) @@ -101,13 +99,11 @@ static auto GetMember(Nonnull arena, Nonnull v, // Found a method. Turn it into a bound method. const FunctionValue& m = cast(**func); return arena->New(&m.declaration(), me_value, - class_type.type_args(), - class_type.witnesses()); + &class_type.bindings()); } else { // Found a class function return arena->New(&(*func)->declaration(), - class_type.type_args(), - class_type.witnesses()); + &class_type.bindings()); } } } @@ -129,8 +125,7 @@ static auto GetMember(Nonnull arena, Nonnull v, << "class function " << f << " not in " << *v; } return arena->New(&(*fun)->declaration(), - class_type.type_args(), - class_type.witnesses()); + &class_type.bindings()); } default: CARBON_FATAL() << "field access not allowed for value " << *v; diff --git a/explorer/interpreter/value.h b/explorer/interpreter/value.h index d3f822cacefc..0f381f7f2072 100644 --- a/explorer/interpreter/value.h +++ b/explorer/interpreter/value.h @@ -11,6 +11,7 @@ #include #include "common/ostream.h" +#include "explorer/ast/bindings.h" #include "explorer/ast/declaration.h" #include "explorer/ast/member.h" #include "explorer/ast/statement.h" @@ -127,9 +128,6 @@ class IntValue : public Value { int value_; }; -using ImplWitnessMap = - std::map, Nonnull>; - // A function value. class FunctionValue : public Value { public: @@ -137,12 +135,10 @@ class FunctionValue : public Value { : Value(Kind::FunctionValue), declaration_(declaration) {} explicit FunctionValue(Nonnull declaration, - const BindingMap& type_args, - const ImplWitnessMap& wits) + Nonnull bindings) : Value(Kind::FunctionValue), declaration_(declaration), - type_args_(type_args), - witnesses_(wits) {} + bindings_(bindings) {} static auto classof(const Value* value) -> bool { return value->kind() == Kind::FunctionValue; @@ -152,14 +148,17 @@ class FunctionValue : public Value { return *declaration_; } - auto type_args() const -> const BindingMap& { return type_args_; } + auto bindings() const -> const Bindings& { return *bindings_; } - auto witnesses() const -> const ImplWitnessMap& { return witnesses_; } + auto type_args() const -> const BindingMap& { return bindings_->args(); } + + auto witnesses() const -> const ImplWitnessMap& { + return bindings_->witnesses(); + } private: Nonnull declaration_; - BindingMap type_args_; - ImplWitnessMap witnesses_; + Nonnull bindings_ = Bindings::None(); }; // A bound method value. It includes the receiver object. @@ -173,13 +172,11 @@ class BoundMethodValue : public Value { explicit BoundMethodValue(Nonnull declaration, Nonnull receiver, - const BindingMap& type_args, - const ImplWitnessMap& wits) + Nonnull bindings) : Value(Kind::BoundMethodValue), declaration_(declaration), receiver_(receiver), - type_args_(type_args), - witnesses_(wits) {} + bindings_(bindings) {} static auto classof(const Value* value) -> bool { return value->kind() == Kind::BoundMethodValue; @@ -191,15 +188,18 @@ class BoundMethodValue : public Value { auto receiver() const -> Nonnull { return receiver_; } - auto type_args() const -> const BindingMap& { return type_args_; } + auto bindings() const -> const Bindings& { return *bindings_; } - auto witnesses() const -> const ImplWitnessMap& { return witnesses_; } + auto type_args() const -> const BindingMap& { return bindings_->args(); } + + auto witnesses() const -> const ImplWitnessMap& { + return bindings_->witnesses(); + } private: Nonnull declaration_; Nonnull receiver_; - BindingMap type_args_; - ImplWitnessMap witnesses_; + Nonnull bindings_ = Bindings::None(); }; // The value of a location in memory. @@ -549,40 +549,36 @@ class NominalClassType : public Value { << "missing arguments for parameterized class type"; } - // Construct a class type that represents the result of applying the - // given generic class to the `type_args`. - explicit NominalClassType(Nonnull declaration, - const BindingMap& type_args) - : Value(Kind::NominalClassType), - declaration_(declaration), - type_args_(type_args) {} - // Construct a fully instantiated generic class type to represent the // run-time type of an object. explicit NominalClassType(Nonnull declaration, - const BindingMap& type_args, - const ImplWitnessMap& wits) + Nonnull bindings) : Value(Kind::NominalClassType), declaration_(declaration), - type_args_(type_args), - witnesses_(wits) {} + bindings_(bindings) {} static auto classof(const Value* value) -> bool { return value->kind() == Kind::NominalClassType; } auto declaration() const -> const ClassDeclaration& { return *declaration_; } - auto type_args() const -> const BindingMap& { return type_args_; } - // Maps each of the class's impl bindings to the witness table - // for the corresponding argument. Should only be called on a fully - // instantiated runtime type of a generic class. - auto witnesses() const -> const ImplWitnessMap& { return witnesses_; } + auto bindings() const -> const Bindings& { return *bindings_; } + + auto type_args() const -> const BindingMap& { return bindings_->args(); } + + // Witnesses for each of the class's impl bindings. These will not in general + // be set for class types that are only intended to be used within + // type-checking and not at runtime, such as in the static_type() of an + // expression or the type in a TypeOfClassType. + auto witnesses() const -> const ImplWitnessMap& { + return bindings_->witnesses(); + } // Returns whether this a parameterized class. That is, a class with // parameters and no corresponding arguments. auto IsParameterized() const -> bool { - return declaration_->type_params().has_value() && type_args_.empty(); + return declaration_->type_params().has_value() && type_args().empty(); } // Returns the value of the function named `name` in this class, or @@ -592,8 +588,7 @@ class NominalClassType : public Value { private: Nonnull declaration_; - BindingMap type_args_; - ImplWitnessMap witnesses_; + Nonnull bindings_ = Bindings::None(); }; // Return the declaration of the member with the given name. @@ -602,7 +597,6 @@ auto FindMember(std::string_view name, -> std::optional>; // An interface type. -// TODO: Consider removing this once ConstraintType is ready. class InterfaceType : public Value { public: explicit InterfaceType(Nonnull declaration) @@ -611,14 +605,10 @@ class InterfaceType : public Value { << "missing arguments for parameterized interface type"; } explicit InterfaceType(Nonnull declaration, - const BindingMap& args) - : Value(Kind::InterfaceType), declaration_(declaration), args_(args) {} - explicit InterfaceType(Nonnull declaration, - const BindingMap& args, const ImplWitnessMap& wits) + Nonnull bindings) : Value(Kind::InterfaceType), declaration_(declaration), - args_(args), - witnesses_(wits) {} + bindings_(bindings) {} static auto classof(const Value* value) -> bool { return value->kind() == Kind::InterfaceType; @@ -627,15 +617,18 @@ class InterfaceType : public Value { auto declaration() const -> const InterfaceDeclaration& { return *declaration_; } - auto args() const -> const BindingMap& { return args_; } - // TODO: These aren't used for anything yet. - auto witnesses() const -> const ImplWitnessMap& { return witnesses_; } + auto bindings() const -> const Bindings& { return *bindings_; } + + auto args() const -> const BindingMap& { return bindings_->args(); } + + auto witnesses() const -> const ImplWitnessMap& { + return bindings_->witnesses(); + } private: Nonnull declaration_; - BindingMap args_; - ImplWitnessMap witnesses_; + Nonnull bindings_ = Bindings::None(); }; // A type-of-type for an unknown constrained type. @@ -733,26 +726,27 @@ class ImplWitness : public Witness { // Construct an instantiated generic impl. explicit ImplWitness(Nonnull declaration, - const BindingMap& type_args, const ImplWitnessMap& wits) + Nonnull bindings) : Witness(Kind::ImplWitness), declaration_(declaration), - type_args_(type_args), - witnesses_(wits) {} + bindings_(bindings) {} static auto classof(const Value* value) -> bool { return value->kind() == Kind::ImplWitness; } auto declaration() const -> const ImplDeclaration& { return *declaration_; } - auto type_args() const -> const BindingMap& { return type_args_; } - // Maps each of the impl's impl bindings to the witness table - // for the corresponding argument. Should only be called on a fully - // instantiated runtime type of a generic class. - auto witnesses() const -> const ImplWitnessMap& { return witnesses_; } + + auto bindings() const -> const Bindings& { return *bindings_; } + + auto type_args() const -> const BindingMap& { return bindings_->args(); } + + auto witnesses() const -> const ImplWitnessMap& { + return bindings_->witnesses(); + } private: Nonnull declaration_; - BindingMap type_args_; - ImplWitnessMap witnesses_; + Nonnull bindings_ = Bindings::None(); }; // A witness table whose concrete value cannot be determined yet.