[torque] Add user-defined Slice struct

This CL consists of several preparatory steps for slices in Torque. Above all, it introduces a user-defined struct, torque_internal::Slice<T>, that performs bounds checking and returns references to elements in arrays. To enable this, several smaller changes were also made:

- Constructors of internal classes such as torque_internal::Reference<T> now require a special 'Unsafe' argument, making it clear that there be dragons.
- Struct methods are now declared during finalization. This allows instances of generic structs to have methods referring to the same struct. Previously, methods would be declared before the instance had been fully registered, leading to errors during type resolution. Furthermore, such methods were declared in a temporary namespace, that would then erroneously escape and lead to use-after-free issues.
- Instances of TypeArgumentInference were not running in the correct (Torque) scopes, leading to type resolution errors.
- The chain of ContextualVariable::Scope for any given ContextualVariable (such as CurrentScope) can now be walked, simplifying debugging.

R=jgruber@chromium.org, tebbi@chromium.org

Bug: v8:7793
Change-Id: I36f808f63cc3ce441062dfc56f511f24f1e3121e
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1758322
Commit-Queue: Georg Schmid <gsps@google.com>
Reviewed-by: Jakob Gruber <jgruber@chromium.org>
Reviewed-by: Tobias Tebbi <tebbi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#63314}
This commit is contained in:
Georg Schmid 2019-08-21 13:47:44 +02:00 committed by Commit Bot
parent 1eda962e59
commit 26e39d12a8
15 changed files with 189 additions and 43 deletions

View File

@ -992,6 +992,8 @@ extern class WasmDebugInfo extends Struct {
extern class WasmExceptionTag extends Struct { index: Smi; }
const kTaggedSize: constexpr int31 generates 'kTaggedSize';
const kSmiTagSize: constexpr int31 generates 'kSmiTagSize';
const V8_INFINITY: constexpr float64 generates 'V8_INFINITY';

View File

@ -3,10 +3,87 @@
// found in the LICENSE file.
namespace torque_internal {
// TODO(gsps): Synthesize SizeOf<T> in the compiler
macro SizeOf<T: type>(): constexpr int31;
SizeOf<Smi>(): constexpr int31 {
return kTaggedSize;
}
// Unsafe is a marker that we require to be passed when calling internal APIs
// // that might lead to unsoundness when used incorrectly. Unsafe markers
// should therefore not be instantiated anywhere outside of this namespace.
struct Unsafe {}
struct Reference<T: type> {
const object: HeapObject;
const offset: intptr;
unsafeMarker: Unsafe;
}
macro UnsafeNewReference<T: type>(object: HeapObject, offset: intptr):&T {
return Reference<T>{
object: object,
offset: offset,
unsafeMarker: Unsafe {}
};
}
struct Slice<T: type> {
Access(index: intptr):&T labels OutOfBounds {
if (Convert<uintptr>(index) < Convert<uintptr>(this.length)) {
return UnsafeNewReference<T>(
this.object, this.offset + index * SizeOf<T>());
} else {
goto OutOfBounds;
}
}
Iterator(): SliceIterator<T> {
const end = this.offset + this.length * SizeOf<T>();
return SliceIterator<T>{
object: this.object,
start: this.offset,
end: end,
unsafeMarker: Unsafe {}
};
}
const object: HeapObject;
const offset: intptr;
const length: intptr;
unsafeMarker: Unsafe;
}
macro UnsafeNewSlice<T: type>(
object: HeapObject, offset: intptr, length: intptr): Slice<T> {
return Slice<T>{
object: object,
offset: offset,
length: length,
unsafeMarker: Unsafe {}
};
}
struct SliceIterator<T: type> {
Empty(): bool {
return this.start == this.end;
}
Next():&T labels NoMore {
if (this.Empty()) {
goto NoMore;
} else {
const result = UnsafeNewReference<T>(this.object, this.start);
this.start += SizeOf<T>();
return result;
}
}
object: HeapObject;
start: intptr;
end: intptr;
unsafeMarker: Unsafe;
}
} // namespace torque_internal

View File

@ -51,6 +51,7 @@ static const char* const CONST_INT32_TYPE_STRING = "constexpr int32";
static const char* const CONST_FLOAT64_TYPE_STRING = "constexpr float64";
static const char* const TORQUE_INTERNAL_NAMESPACE_STRING = "torque_internal";
static const char* const REFERENCE_TYPE_STRING = "Reference";
static const char* const STRUCT_NAMESPACE_STRING = "_struct";
inline bool IsConstexprName(const std::string& name) {
return name.substr(0, std::strlen(CONSTEXPR_TYPE_PREFIX)) ==

View File

@ -15,7 +15,7 @@ namespace internal {
namespace torque {
template <class Variable>
V8_EXPORT_PRIVATE typename Variable::VariableType*& ContextualVariableTop();
V8_EXPORT_PRIVATE typename Variable::Scope*& ContextualVariableTop();
// {ContextualVariable} provides a clean alternative to a global variable.
// The contextual variable is mutable, and supports managing the value of
@ -30,8 +30,6 @@ V8_EXPORT_PRIVATE typename Variable::VariableType*& ContextualVariableTop();
template <class Derived, class VarType>
class ContextualVariable {
public:
using VariableType = VarType;
// A {Scope} contains a new object of type {VarType} and gives
// ContextualVariable::Get() access to it. Upon destruction, the contextual
// variable is restored to the state before the {Scope} was created. Scopes
@ -41,18 +39,20 @@ class ContextualVariable {
public:
template <class... Args>
explicit Scope(Args&&... args)
: current_(std::forward<Args>(args)...), previous_(Top()) {
Top() = &current_;
: value_(std::forward<Args>(args)...), previous_(Top()) {
Top() = this;
}
~Scope() {
// Ensure stack discipline.
DCHECK_EQ(&current_, Top());
DCHECK_EQ(this, Top());
Top() = previous_;
}
VarType& Value() { return value_; }
private:
VarType current_;
VarType* previous_;
VarType value_;
Scope* previous_;
static_assert(std::is_base_of<ContextualVariable, Derived>::value,
"Curiously Recurring Template Pattern");
@ -65,13 +65,13 @@ class ContextualVariable {
// for this contextual variable.
static VarType& Get() {
DCHECK_NOT_NULL(Top());
return *Top();
return Top()->Value();
}
private:
template <class T>
friend typename T::VariableType*& ContextualVariableTop();
static VarType*& Top() { return ContextualVariableTop<Derived>(); }
friend typename T::Scope*& ContextualVariableTop();
static Scope*& Top() { return ContextualVariableTop<Derived>(); }
static bool HasScope() { return Top() != nullptr; }
friend class MessageBuilder;
@ -82,11 +82,11 @@ class ContextualVariable {
struct VarName \
: v8::internal::torque::ContextualVariable<VarName, __VA_ARGS__> {}
#define DEFINE_CONTEXTUAL_VARIABLE(VarName) \
template <> \
V8_EXPORT_PRIVATE VarName::VariableType*& ContextualVariableTop<VarName>() { \
static thread_local VarName::VariableType* top = nullptr; \
return top; \
#define DEFINE_CONTEXTUAL_VARIABLE(VarName) \
template <> \
V8_EXPORT_PRIVATE VarName::Scope*& ContextualVariableTop<VarName>() { \
static thread_local VarName::Scope* top = nullptr; \
return top; \
}
// By inheriting from {ContextualClass} a class can become a contextual variable

View File

@ -75,6 +75,7 @@ TypeArgumentInference Generic::InferSpecializationTypes(
std::vector<TypeExpression*> explicit_parameters(
parameters.begin() + implicit_count, parameters.end());
CurrentScope::Scope generic_scope(ParentScope());
TypeArgumentInference inference(generic_parameters(),
explicit_specialization_types,
explicit_parameters, arguments);

View File

@ -20,6 +20,10 @@ TypeArgumentInference::TypeArgumentInference(
Fail("more explicit type arguments than expected");
return;
}
if (term_parameters.size() != term_argument_types.size()) {
Fail("number of term parameters does not match number of term arguments!");
return;
}
for (size_t i = 0; i < type_parameters.size(); i++) {
type_parameter_from_name_[type_parameters[i]->value] = i;
@ -28,7 +32,6 @@ TypeArgumentInference::TypeArgumentInference(
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;

View File

@ -48,6 +48,11 @@ namespace torque {
// In the above case the inference simply ignores inconsistent constraints on
// `T`. Similarly, we ignore all constraints arising from formal parameters
// that are function- or union-typed.
//
// Finally, note that term parameters are passed as type expressions, since
// we have no way of expressing a reference to type parameter as a Type. These
// type expressions are resolved during matching, so TypeArgumentInference
// should be instantiated in the appropriate scope.
class TypeArgumentInference {
public:
TypeArgumentInference(const NameVector& type_parameters,

View File

@ -39,17 +39,6 @@ const StructType* TypeOracle::GetGenericStructTypeInstance(
return *specialization;
} else {
CurrentScope::Scope generic_scope(generic_struct->ParentScope());
// Create a temporary fake-namespace just to temporarily declare the
// specialization aliases for the generic types to create a signature.
Namespace tmp_namespace("_tmp");
CurrentScope::Scope tmp_namespace_scope(&tmp_namespace);
auto arg_types_iterator = arg_types.begin();
for (auto param : params) {
TypeAlias* alias = Declarations::DeclareType(param, *arg_types_iterator);
alias->SetIsUserDefined(false);
arg_types_iterator++;
}
auto struct_type = TypeVisitor::ComputeType(generic_struct->declaration(),
{{generic_struct, arg_types}});
specializations.Add(arg_types, struct_type);

View File

@ -31,11 +31,12 @@ class TypeOracle : public ContextualClass<TypeOracle> {
}
static StructType* GetStructType(
const std::string& basename,
const StructDeclaration* decl,
StructType::MaybeSpecializationKey specialized_from) {
StructType* result =
new StructType(CurrentNamespace(), basename, specialized_from);
Namespace* nspace = new Namespace(STRUCT_NAMESPACE_STRING);
StructType* result = new StructType(nspace, decl, specialized_from);
Get().aggregate_types_.push_back(std::unique_ptr<StructType>(result));
Get().struct_namespaces_.push_back(std::unique_ptr<Namespace>(nspace));
return result;
}
@ -260,6 +261,7 @@ class TypeOracle : public ContextualClass<TypeOracle> {
std::vector<std::unique_ptr<Type>> nominal_types_;
std::vector<std::unique_ptr<AggregateType>> aggregate_types_;
std::vector<std::unique_ptr<Type>> top_types_;
std::vector<std::unique_ptr<Namespace>> struct_namespaces_;
};
} // namespace torque

View File

@ -110,9 +110,20 @@ void DeclareMethods(AggregateType* container_type,
const StructType* TypeVisitor::ComputeType(
StructDeclaration* decl,
StructType::MaybeSpecializationKey specialized_from) {
StructType* struct_type = TypeOracle::GetStructType(decl, specialized_from);
CurrentScope::Scope struct_namespace_scope(struct_type->nspace());
CurrentSourcePosition::Scope position_activator(decl->pos);
StructType* struct_type =
TypeOracle::GetStructType(decl->name->value, specialized_from);
if (specialized_from) {
auto& params = specialized_from->generic->generic_parameters();
auto arg_types_iterator = specialized_from->specialized_types.begin();
for (auto param : params) {
TypeAlias* alias = Declarations::DeclareType(param, *arg_types_iterator);
alias->SetIsUserDefined(false);
arg_types_iterator++;
}
}
size_t offset = 0;
for (auto& field : decl->fields) {
CurrentSourcePosition::Scope position_activator(
@ -132,7 +143,6 @@ const StructType* TypeVisitor::ComputeType(
false});
offset += LoweredSlotCount(field_type);
}
DeclareMethods(struct_type, decl->methods);
return struct_type;
}
@ -318,6 +328,11 @@ void TypeVisitor::VisitClassFieldsAndMethods(
DeclareMethods(class_type, class_declaration->methods);
}
void TypeVisitor::VisitStructMethods(
StructType* struct_type, const StructDeclaration* struct_declaration) {
DeclareMethods(struct_type, struct_declaration->methods);
}
const StructType* TypeVisitor::ComputeTypeForStructExpression(
TypeExpression* type_expression,
const std::vector<const Type*>& term_argument_types) {
@ -350,9 +365,11 @@ const StructType* TypeVisitor::ComputeTypeForStructExpression(
term_parameters.push_back(field.name_and_type.type);
}
CurrentScope::Scope generic_scope(generic_struct->ParentScope());
TypeArgumentInference inference(
generic_struct->declaration()->generic_parameters,
explicit_type_arguments, term_parameters, term_argument_types);
if (inference.HasFailed()) {
ReportError("failed to infer type arguments for struct ", basic->name,
" initialization: ", inference.GetFailureReason());

View File

@ -27,6 +27,8 @@ class TypeVisitor {
static const Type* ComputeType(TypeExpression* type_expression);
static void VisitClassFieldsAndMethods(
ClassType* class_type, const ClassDeclaration* class_declaration);
static void VisitStructMethods(StructType* struct_type,
const StructDeclaration* struct_declaration);
static Signature MakeSignature(const CallableDeclaration* declaration);
static const StructType* ComputeTypeForStructExpression(
TypeExpression* type_expression,

View File

@ -298,7 +298,7 @@ std::string StructType::MangledName() const {
std::stringstream result;
// TODO(gsps): Add 'ST' as a prefix once we can control the generated type
// name from Torque code
result << basename_;
result << decl_->name->value;
if (specialized_from_) {
for (const Type* t : specialized_from_->specialized_types) {
std::string arg_type_string = t->MangledName();
@ -345,6 +345,17 @@ std::string StructType::ToExplicitString() const {
return result.str();
}
void StructType::Finalize() const {
if (is_finalized_) return;
{
CurrentScope::Scope scope_activator(nspace());
CurrentSourcePosition::Scope position_activator(decl_->pos);
TypeVisitor::VisitStructMethods(const_cast<StructType*>(this), decl_);
}
is_finalized_ = true;
CheckForDuplicateFields();
}
constexpr ClassFlags ClassType::kInternalFlags;
ClassType::ClassType(const Type* parent, Namespace* nspace,

View File

@ -486,22 +486,19 @@ class StructType final : public AggregateType {
private:
friend class TypeOracle;
StructType(Namespace* nspace, const std::string& basename,
StructType(Namespace* nspace, const StructDeclaration* decl,
MaybeSpecializationKey specialized_from = base::nullopt)
: AggregateType(Kind::kStructType, nullptr, nspace,
ComputeName(basename, specialized_from)),
basename_(basename),
ComputeName(decl->name->value, specialized_from)),
decl_(decl),
specialized_from_(specialized_from) {}
void Finalize() const override {
is_finalized_ = true;
CheckForDuplicateFields();
}
void Finalize() const override;
static std::string ComputeName(const std::string& basename,
MaybeSpecializationKey specialized_from);
std::string basename_;
const StructDeclaration* decl_;
MaybeSpecializationKey specialized_from_;
};

View File

@ -484,6 +484,20 @@ TEST(TestReferences) {
ft.Call();
}
TEST(TestSlices) {
CcTest::InitializeVM();
Isolate* isolate(CcTest::i_isolate());
i::HandleScope scope(isolate);
CodeAssemblerTester asm_tester(isolate);
TestTorqueAssembler m(asm_tester.state());
{
m.TestSlices();
m.Return(m.UndefinedConstant());
}
FunctionTester ft(asm_tester.GenerateCode(), 0);
ft.Call();
}
TEST(TestStaticAssert) {
CcTest::InitializeVM();
Isolate* isolate(CcTest::i_isolate());

View File

@ -842,6 +842,31 @@ namespace test {
check(array.b == 9);
}
@export
macro TestSlices() {
const fixedArray: FixedArray = AllocateZeroedFixedArray(3);
// TODO(gsps): Directly reference fixedArray.objects once supported
let slice = torque_internal::UnsafeNewSlice<Smi>(
fixedArray,
(& fixedArray.length).offset + torque_internal::SizeOf<Smi>(), 3);
for (let i: intptr = 0; i < slice.length; i++) {
let ref = slice.Access(i) otherwise unreachable;
check(* ref == 0);
* ref = Convert<Smi>(i) + 7;
}
let it = slice.Iterator();
let count: Smi = 0;
while (true) {
let ref = it.Next() otherwise break;
check(* ref == count + 7);
count++;
}
check(count == 3);
check(it.Empty());
}
@export
macro TestStaticAssert() {
StaticAssert(1 + 2 == 3);