[torque] Generalize type argument inference for generic calls

With the arrival of generic structs (https://chromium-review.googlesource.com/c/v8/v8/+/1714868) the existing type inference procedure for generic calls became incomplete, since it could not infer types that were only constrained as part of generic types. For instance, given

  struct Box<T: Type> { ... }

  macro unbox<T: type>(box: Box<T>): T

the type argument (Smi) at the following call site

  const box: Box<Smi> = ...;
  unbox(box);

could not be inferred.

This CL re-implements the inference procedure and documents the semantics of type argument inference in Torque a bit more clearly.

R=tebbi@chromium.org

Change-Id: I868f16afbd9864b9c810ac49bc1639b467df939c
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1720812
Commit-Queue: Georg Schmid <gsps@google.com>
Reviewed-by: Tobias Tebbi <tebbi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#63005}
This commit is contained in:
Georg Schmid 2019-07-31 14:01:55 +02:00 committed by Commit Bot
parent 3a1bb751c9
commit d4e6525849
8 changed files with 249 additions and 65 deletions

View File

@ -3364,6 +3364,8 @@ v8_source_set("torque_base") {
"src/torque/torque-compiler.h", "src/torque/torque-compiler.h",
"src/torque/torque-parser.cc", "src/torque/torque-parser.cc",
"src/torque/torque-parser.h", "src/torque/torque-parser.h",
"src/torque/type-inference.cc",
"src/torque/type-inference.h",
"src/torque/type-oracle.cc", "src/torque/type-oracle.cc",
"src/torque/type-oracle.h", "src/torque/type-oracle.h",
"src/torque/type-visitor.cc", "src/torque/type-visitor.cc",

View File

@ -7,6 +7,7 @@
#include "src/torque/declarable.h" #include "src/torque/declarable.h"
#include "src/torque/global-context.h" #include "src/torque/global-context.h"
#include "src/torque/type-inference.h"
#include "src/torque/type-visitor.h" #include "src/torque/type-visitor.h"
namespace v8 { namespace v8 {
@ -65,56 +66,20 @@ std::ostream& operator<<(std::ostream& os, const Generic& g) {
return os; return os;
} }
namespace { TypeArgumentInference Generic::InferSpecializationTypes(
base::Optional<const Type*> InferTypeArgument(const std::string& to_infer,
TypeExpression* parameter,
const Type* argument) {
BasicTypeExpression* basic = BasicTypeExpression::DynamicCast(parameter);
if (basic && basic->namespace_qualification.empty() && !basic->is_constexpr &&
basic->name == to_infer) {
return argument;
}
// TODO(gsps): Perform type inference for generic types
return base::nullopt;
}
base::Optional<const Type*> InferTypeArgument(
const std::string& to_infer, const std::vector<TypeExpression*>& parameters,
const TypeVector& arguments) {
for (size_t i = 0; i < arguments.size() && i < parameters.size(); ++i) {
if (base::Optional<const Type*> inferred =
InferTypeArgument(to_infer, parameters[i], arguments[i])) {
return *inferred;
}
}
return base::nullopt;
}
} // namespace
base::Optional<TypeVector> Generic::InferSpecializationTypes(
const TypeVector& explicit_specialization_types, const TypeVector& explicit_specialization_types,
const TypeVector& arguments) { const TypeVector& arguments) {
TypeVector result = explicit_specialization_types;
size_t type_parameter_count = declaration()->generic_parameters.size();
if (explicit_specialization_types.size() > type_parameter_count) {
return base::nullopt;
}
for (size_t i = explicit_specialization_types.size();
i < type_parameter_count; ++i) {
const std::string type_name = declaration()->generic_parameters[i]->value;
size_t implicit_count = size_t implicit_count =
declaration()->callable->signature->parameters.implicit_count; declaration()->callable->signature->parameters.implicit_count;
const std::vector<TypeExpression*>& parameters = const std::vector<TypeExpression*>& parameters =
declaration()->callable->signature->parameters.types; declaration()->callable->signature->parameters.types;
std::vector<TypeExpression*> explicit_parameters( std::vector<TypeExpression*> explicit_parameters(
parameters.begin() + implicit_count, parameters.end()); parameters.begin() + implicit_count, parameters.end());
base::Optional<const Type*> inferred =
InferTypeArgument(type_name, explicit_parameters, arguments); TypeArgumentInference inference(declaration()->generic_parameters,
if (!inferred) return base::nullopt; explicit_specialization_types,
result.push_back(*inferred); explicit_parameters, arguments);
} return inference;
return result;
} }
bool Namespace::IsDefaultNamespace() const { bool Namespace::IsDefaultNamespace() const {

View File

@ -21,6 +21,7 @@ namespace torque {
class Scope; class Scope;
class Namespace; class Namespace;
class TypeArgumentInference;
DECLARE_CONTEXTUAL_VARIABLE(CurrentScope, Scope*); DECLARE_CONTEXTUAL_VARIABLE(CurrentScope, Scope*);
@ -489,7 +490,7 @@ class Generic : public Declarable {
} }
SpecializationMap<Callable>& specializations() { return specializations_; } SpecializationMap<Callable>& specializations() { return specializations_; }
base::Optional<TypeVector> InferSpecializationTypes( TypeArgumentInference InferSpecializationTypes(
const TypeVector& explicit_specialization_types, const TypeVector& explicit_specialization_types,
const TypeVector& arguments); const TypeVector& arguments);

View File

@ -10,6 +10,7 @@
#include "src/torque/implementation-visitor.h" #include "src/torque/implementation-visitor.h"
#include "src/torque/parameter-difference.h" #include "src/torque/parameter-difference.h"
#include "src/torque/server-data.h" #include "src/torque/server-data.h"
#include "src/torque/type-inference.h"
#include "src/torque/type-visitor.h" #include "src/torque/type-visitor.h"
namespace v8 { namespace v8 {
@ -1582,7 +1583,9 @@ namespace {
void FailCallableLookup(const std::string& reason, const QualifiedName& name, void FailCallableLookup(const std::string& reason, const QualifiedName& name,
const TypeVector& parameter_types, const TypeVector& parameter_types,
const std::vector<Binding<LocalLabel>*>& labels, const std::vector<Binding<LocalLabel>*>& labels,
const std::vector<Signature>& candidates) { const std::vector<Signature>& candidates,
const std::vector<std::tuple<Generic*, const char*>>
inapplicable_generics) {
std::stringstream stream; std::stringstream stream;
stream << "\n" << reason << ": \n " << name << "(" << parameter_types << ")"; stream << "\n" << reason << ": \n " << name << "(" << parameter_types << ")";
if (labels.size() != 0) { if (labels.size() != 0) {
@ -1596,6 +1599,16 @@ void FailCallableLookup(const std::string& reason, const QualifiedName& name,
stream << "\n " << name; stream << "\n " << name;
PrintSignature(stream, signature, false); PrintSignature(stream, signature, false);
} }
if (inapplicable_generics.size() != 0) {
stream << "\nfailed to instantiate all of these generic declarations:";
for (auto& failure : inapplicable_generics) {
Generic* generic;
const char* reason;
std::tie(generic, reason) = failure;
stream << "\n " << generic->name() << " defined at "
<< generic->Position() << ":\n " << reason << "\n";
}
}
ReportError(stream.str()); ReportError(stream.str());
} }
@ -1655,17 +1668,20 @@ Callable* ImplementationVisitor::LookupCallable(
std::vector<Declarable*> overloads; std::vector<Declarable*> overloads;
std::vector<Signature> overload_signatures; std::vector<Signature> overload_signatures;
std::vector<std::tuple<Generic*, const char*>> inapplicable_generics;
for (auto* declarable : declaration_container) { for (auto* declarable : declaration_container) {
if (Generic* generic = Generic::DynamicCast(declarable)) { if (Generic* generic = Generic::DynamicCast(declarable)) {
base::Optional<TypeVector> inferred_specialization_types = TypeArgumentInference inference = generic->InferSpecializationTypes(
generic->InferSpecializationTypes(specialization_types, specialization_types, parameter_types);
parameter_types); if (inference.HasFailed()) {
if (!inferred_specialization_types) continue; inapplicable_generics.push_back(
std::make_tuple(generic, inference.GetFailureReason()));
continue;
}
overloads.push_back(generic); overloads.push_back(generic);
overload_signatures.push_back( overload_signatures.push_back(
DeclarationVisitor::MakeSpecializedSignature( DeclarationVisitor::MakeSpecializedSignature(
SpecializationKey<Generic>{generic, SpecializationKey<Generic>{generic, inference.GetResult()}));
*inferred_specialization_types}));
} else if (Callable* callable = Callable::DynamicCast(declarable)) { } else if (Callable* callable = Callable::DynamicCast(declarable)) {
overloads.push_back(callable); overloads.push_back(callable);
overload_signatures.push_back(callable->signature()); overload_signatures.push_back(callable->signature());
@ -1684,7 +1700,7 @@ Callable* ImplementationVisitor::LookupCallable(
} }
} }
if (overloads.empty()) { if (overloads.empty() && inapplicable_generics.empty()) {
if (silence_errors) return nullptr; if (silence_errors) return nullptr;
std::stringstream stream; std::stringstream stream;
stream << "no matching declaration found for " << name; stream << "no matching declaration found for " << name;
@ -1692,7 +1708,8 @@ Callable* ImplementationVisitor::LookupCallable(
} else if (candidates.empty()) { } else if (candidates.empty()) {
if (silence_errors) return nullptr; if (silence_errors) return nullptr;
FailCallableLookup("cannot find suitable callable with name", name, FailCallableLookup("cannot find suitable callable with name", name,
parameter_types, labels, overload_signatures); parameter_types, labels, overload_signatures,
inapplicable_generics);
} }
auto is_better_candidate = [&](size_t a, size_t b) { auto is_better_candidate = [&](size_t a, size_t b) {
@ -1713,14 +1730,15 @@ Callable* ImplementationVisitor::LookupCallable(
candidate_signatures.push_back(overload_signatures[i]); candidate_signatures.push_back(overload_signatures[i]);
} }
FailCallableLookup("ambiguous callable ", name, parameter_types, labels, FailCallableLookup("ambiguous callable ", name, parameter_types, labels,
candidate_signatures); candidate_signatures, inapplicable_generics);
} }
} }
if (Generic* generic = Generic::DynamicCast(overloads[best])) { if (Generic* generic = Generic::DynamicCast(overloads[best])) {
result = GetOrCreateSpecialization(SpecializationKey<Generic>{ TypeArgumentInference inference = generic->InferSpecializationTypes(
generic, *generic->InferSpecializationTypes(specialization_types, specialization_types, parameter_types);
parameter_types)}); result = GetOrCreateSpecialization(
SpecializationKey<Generic>{generic, inference.GetResult()});
} else { } else {
result = Callable::cast(overloads[best]); result = Callable::cast(overloads[best]);
} }

View File

@ -0,0 +1,118 @@
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/torque/type-inference.h"
namespace v8 {
namespace internal {
namespace torque {
TypeArgumentInference::TypeArgumentInference(
const NameVector& type_parameters,
const TypeVector& explicit_type_arguments,
const std::vector<TypeExpression*> term_parameters,
const TypeVector& term_argument_types)
: num_explicit_(explicit_type_arguments.size()),
type_parameter_from_name_(type_parameters.size()),
inferred_(type_parameters.size()) {
if (num_explicit_ > type_parameters.size()) {
Fail("more explicit type arguments than expected");
return;
}
for (size_t i = 0; i < type_parameters.size(); i++) {
type_parameter_from_name_[type_parameters[i]->value] = i;
}
for (size_t i = 0; i < num_explicit_; i++) {
inferred_[i] = {explicit_type_arguments[i]};
}
DCHECK_EQ(term_parameters.size(), term_argument_types.size());
for (size_t i = 0; i < term_parameters.size(); i++) {
Match(term_parameters[i], term_argument_types[i]);
if (HasFailed()) return;
}
for (size_t i = 0; i < type_parameters.size(); i++) {
if (!inferred_[i]) {
Fail("failed to infer arguments for all type parameters");
return;
}
}
}
TypeVector TypeArgumentInference::GetResult() const {
CHECK(!HasFailed());
TypeVector result(inferred_.size());
std::transform(
inferred_.begin(), inferred_.end(), result.begin(),
[](base::Optional<const Type*> maybe_type) { return *maybe_type; });
return result;
}
void TypeArgumentInference::Match(TypeExpression* parameter,
const Type* argument_type) {
if (BasicTypeExpression* basic =
BasicTypeExpression::DynamicCast(parameter)) {
// If the parameter is referring to one of the type parameters, substitute
if (basic->namespace_qualification.empty() && !basic->is_constexpr) {
auto result = type_parameter_from_name_.find(basic->name);
if (result != type_parameter_from_name_.end()) {
size_t type_parameter_index = result->second;
if (type_parameter_index < num_explicit_) {
return;
}
base::Optional<const Type*>& maybe_inferred =
inferred_[type_parameter_index];
if (maybe_inferred && *maybe_inferred != argument_type) {
Fail("found conflicting types for generic parameter");
} else {
inferred_[type_parameter_index] = {argument_type};
}
return;
}
}
// Try to recurse in case of generic types
if (!basic->generic_arguments.empty()) {
auto* argument_struct_type = StructType::DynamicCast(argument_type);
if (argument_struct_type) {
MatchGeneric(basic, argument_struct_type);
}
}
// NOTE: We could also check whether ground parameter types match the
// argument types, but we are only interested in inferring type arguments
// here
} else {
Fail("unsupported parameter expression");
}
}
void TypeArgumentInference::MatchGeneric(BasicTypeExpression* parameter,
const StructType* argument_type) {
QualifiedName qualified_name{parameter->namespace_qualification,
parameter->name};
GenericStructType* generic_struct =
Declarations::LookupUniqueGenericStructType(qualified_name);
auto& specialized_from = argument_type->GetSpecializedFrom();
if (!specialized_from || specialized_from->generic != generic_struct) {
return Fail("found conflicting generic type constructors");
}
auto& parameters = parameter->generic_arguments;
auto& argument_types = specialized_from->specialized_types;
if (parameters.size() != argument_types.size()) {
Error(
"cannot infer types from generic-struct-typed parameter with "
"incompatible number of arguments")
.Position(parameter->pos)
.Throw();
}
for (size_t i = 0; i < parameters.size(); i++) {
Match(parameters[i], argument_types[i]);
if (HasFailed()) return;
}
}
} // namespace torque
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,78 @@
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_TORQUE_TYPE_INFERENCE_H_
#define V8_TORQUE_TYPE_INFERENCE_H_
#include <string>
#include <unordered_map>
#include "src/base/optional.h"
#include "src/torque/ast.h"
#include "src/torque/declarations.h"
#include "src/torque/types.h"
namespace v8 {
namespace internal {
namespace torque {
// Type argument inference computes a potential instantiation of a generic
// callable given some concrete argument types. As an example, consider the
// generic macro
//
// macro Pick<T: type>(x: T, y: T): T
//
// along with a given call site, such as
//
// Pick(1, 2);
//
// The inference proceeds by matching the term argument types (`constexpr
// int31`, in case of `1` and `2`) against the formal parameter types (`T` in
// both cases). During this matching we discover that `T` must equal `constexpr
// int31`.
//
// The inference will not perform any comprehensive type checking of its own,
// but *does* fail if type parameters cannot be soundly instantiated given the
// call site. For instance, for the following call site
//
// const aSmi: Smi = ...;
// Pick(1, aSmi); // inference fails
//
// inference would fail, since `constexpr int31` is distinct from `Smi`. To
// allow for implicit conversions to be tried in a separate step after type
// argument inference, a number of type arguments may be given explicitly:
//
// Pick<Smi>(1, aSmi); // inference succeeds (doing nothing)
//
// In the above case the inference simply ignores inconsistent constraints on
// `T`.
class TypeArgumentInference {
public:
TypeArgumentInference(const NameVector& type_parameters,
const TypeVector& explicit_type_arguments,
const std::vector<TypeExpression*> term_parameters,
const TypeVector& term_argument_types);
bool HasFailed() const { return failure_reason_.has_value(); }
const char* GetFailureReason() { return *failure_reason_; }
TypeVector GetResult() const;
private:
void Fail(const char* reason) { failure_reason_ = {reason}; }
void Match(TypeExpression* parameter, const Type* argument_type);
void MatchGeneric(BasicTypeExpression* parameter,
const StructType* argument_type);
size_t num_explicit_;
std::unordered_map<std::string, size_t> type_parameter_from_name_;
std::vector<base::Optional<const Type*>> inferred_;
base::Optional<const char*> failure_reason_;
};
} // namespace torque
} // namespace internal
} // namespace v8
#endif // V8_TORQUE_TYPE_INFERENCE_H_

View File

@ -475,6 +475,9 @@ class StructType final : public AggregateType {
std::string ToExplicitString() const override; std::string ToExplicitString() const override;
std::string GetGeneratedTypeNameImpl() const override; std::string GetGeneratedTypeNameImpl() const override;
std::string MangledName() const override; std::string MangledName() const override;
const MaybeSpecializationKey& GetSpecializedFrom() const {
return specialized_from_;
}
static base::Optional<const Type*> MatchUnaryGeneric( static base::Optional<const Type*> MatchUnaryGeneric(
const Type* type, GenericStructType* generic); const Type* type, GenericStructType* generic);

View File

@ -898,8 +898,7 @@ namespace test {
const ref:&Smi = & array.a; const ref:&Smi = & array.a;
* ref = 3 + * ref; * ref = 3 + * ref;
-- * ref; -- * ref;
// TODO(gsps): Remove explicit type arg once inference works Swap(& array.b, array.GetA());
Swap<Smi>(& array.b, array.GetA());
check(array.a == 2); check(array.a == 2);
check(array.b == 9); check(array.b == 9);
} }
@ -1002,7 +1001,7 @@ namespace test {
@export @export
macro TestGenericStruct2(): TestTuple<Smi, intptr> { macro TestGenericStruct2(): TestTuple<Smi, intptr> {
const intptrAndSmi = TestTuple<intptr, Smi>{fst: 1, snd: 2}; const intptrAndSmi = TestTuple<intptr, Smi>{fst: 1, snd: 2};
const smiAndIntptr = TupleSwap<intptr, Smi>(intptrAndSmi); const smiAndIntptr = TupleSwap(intptrAndSmi);
check(intptrAndSmi.fst == smiAndIntptr.snd); check(intptrAndSmi.fst == smiAndIntptr.snd);
check(intptrAndSmi.snd == smiAndIntptr.fst); check(intptrAndSmi.snd == smiAndIntptr.fst);
return smiAndIntptr; return smiAndIntptr;