From b75940132899c4ccdced29e0d0a98161963982e1 Mon Sep 17 00:00:00 2001 From: "Jeremy G. Siek" Date: Sat, 11 Jun 2022 18:47:07 -0400 Subject: [PATCH] Heap allocation basics (#1323) * initial implementation of new and delete * remove requirement for empty heap from trace.carbon * more tests * fixed substitution for generic function types --- common/fuzzing/carbon.proto | 2 + common/fuzzing/proto_to_carbon.cpp | 6 + explorer/ast/expression.cpp | 21 +++- explorer/ast/expression.h | 4 +- explorer/ast/impl_binding.h | 12 ++ explorer/ast/pattern.h | 12 ++ explorer/data/prelude.carbon | 11 ++ explorer/fuzzing/ast_to_proto.cpp | 6 + explorer/interpreter/heap.cpp | 2 + explorer/interpreter/heap.h | 1 + explorer/interpreter/interpreter.cpp | 32 ++++- explorer/interpreter/type_checker.cpp | 114 +++++++++++++++--- explorer/interpreter/value.cpp | 60 +++++++-- .../testdata/addr/fail-method-me-type.carbon | 2 +- explorer/testdata/basic_syntax/trace.carbon | 4 - .../generic_function/generic_method.carbon | 45 +++++++ .../testdata/impl/generic_method_impl.carbon | 49 ++++++++ explorer/testdata/pointer/aliasing.carbon | 21 ++++ .../pointer/fail_use_after_free.carbon | 18 +++ .../pointer/heap_alloc_lifetime.carbon | 26 ++++ .../testdata/pointer/intrinsic_new.carbon | 19 +++ .../testdata/pointer/new_and_delete.carbon | 19 +++ 22 files changed, 436 insertions(+), 50 deletions(-) create mode 100644 explorer/testdata/generic_function/generic_method.carbon create mode 100644 explorer/testdata/impl/generic_method_impl.carbon create mode 100644 explorer/testdata/pointer/aliasing.carbon create mode 100644 explorer/testdata/pointer/fail_use_after_free.carbon create mode 100644 explorer/testdata/pointer/heap_alloc_lifetime.carbon create mode 100644 explorer/testdata/pointer/intrinsic_new.carbon create mode 100644 explorer/testdata/pointer/new_and_delete.carbon diff --git a/common/fuzzing/carbon.proto b/common/fuzzing/carbon.proto index 8676e759c1d8..8ba1e4055ad7 100644 --- a/common/fuzzing/carbon.proto +++ b/common/fuzzing/carbon.proto @@ -83,6 +83,8 @@ message IntrinsicExpression { enum Intrinsic { UnknownIntrinsic = 0; Print = 1; + Alloc = 2; + Dealloc = 3; } optional Intrinsic intrinsic = 1; optional TupleLiteralExpression argument = 2; diff --git a/common/fuzzing/proto_to_carbon.cpp b/common/fuzzing/proto_to_carbon.cpp index cd76deb8da23..81df675a2440 100644 --- a/common/fuzzing/proto_to_carbon.cpp +++ b/common/fuzzing/proto_to_carbon.cpp @@ -286,6 +286,12 @@ static auto ExpressionToCarbon(const Fuzzing::Expression& expression, case Fuzzing::IntrinsicExpression::Print: out << "__intrinsic_print"; break; + case Fuzzing::IntrinsicExpression::Alloc: + out << "__intrinsic_new"; + break; + case Fuzzing::IntrinsicExpression::Dealloc: + out << "__intrinsic_delete"; + break; } TupleLiteralExpressionToCarbon(intrinsic.argument(), out); } break; diff --git a/explorer/ast/expression.cpp b/explorer/ast/expression.cpp index ba860d382e91..ea1dff70914a 100644 --- a/explorer/ast/expression.cpp +++ b/explorer/ast/expression.cpp @@ -21,8 +21,10 @@ using llvm::isa; auto IntrinsicExpression::FindIntrinsic(std::string_view name, SourceLocation source_loc) -> ErrorOr { - static const auto& intrinsic_map = - *new std::map({{"print", Intrinsic::Print}}); + static const auto& intrinsic_map = *new std::map( + {{"print", Intrinsic::Print}, + {"new", Intrinsic::Alloc}, + {"delete", Intrinsic::Dealloc}}); name.remove_prefix(std::strlen("__intrinsic_")); auto it = intrinsic_map.find(name); if (it == intrinsic_map.end()) { @@ -157,14 +159,23 @@ void Expression::Print(llvm::raw_ostream& out) const { out << "fn " << fn.parameter() << " -> " << fn.return_type(); break; } - case ExpressionKind::IntrinsicExpression: - out << "intrinsic_expression("; + case ExpressionKind::IntrinsicExpression: { + const auto& iexp = cast(*this); + out << "intrinsic_"; switch (cast(*this).intrinsic()) { case IntrinsicExpression::Intrinsic::Print: out << "print"; + break; + case IntrinsicExpression::Intrinsic::Alloc: + out << "new"; + break; + case IntrinsicExpression::Intrinsic::Dealloc: + out << "delete"; + break; } - out << ")"; + out << iexp.args(); break; + } case ExpressionKind::IfExpression: { const auto& if_expr = cast(*this); out << "if " << if_expr.condition() << " then " diff --git a/explorer/ast/expression.h b/explorer/ast/expression.h index 6436e1f036d0..573c2013dbd5 100644 --- a/explorer/ast/expression.h +++ b/explorer/ast/expression.h @@ -601,9 +601,7 @@ class ValueLiteral : public Expression { class IntrinsicExpression : public Expression { public: - enum class Intrinsic { - Print, - }; + enum class Intrinsic { Print, Alloc, Dealloc }; // Returns the enumerator corresponding to the intrinsic named `name`, // or raises a fatal compile error if there is no such enumerator. diff --git a/explorer/ast/impl_binding.h b/explorer/ast/impl_binding.h index 8a3da95c5fea..3204cccb829c 100644 --- a/explorer/ast/impl_binding.h +++ b/explorer/ast/impl_binding.h @@ -67,10 +67,22 @@ class ImplBinding : public AstNode { } auto value_category() const -> ValueCategory { return ValueCategory::Let; } + // Return the original impl binding. + auto original() const -> Nonnull { + if (original_.has_value()) + return *original_; + else + return this; + } + + // Set the original impl binding. + void set_original(Nonnull orig) { original_ = orig; } + private: Nonnull type_var_; Nonnull iface_; std::optional> static_type_; + std::optional> original_; }; } // namespace Carbon diff --git a/explorer/ast/pattern.h b/explorer/ast/pattern.h index cd1ef4246fa7..7ae38a6690ca 100644 --- a/explorer/ast/pattern.h +++ b/explorer/ast/pattern.h @@ -56,6 +56,7 @@ class Pattern : public AstNode { CARBON_CHECK(static_type_.has_value()); return **static_type_; } + auto has_static_type() const -> bool { return static_type_.has_value(); } // Sets the static type of this expression. Can only be called once, during // typechecking. @@ -266,11 +267,22 @@ class GenericBinding : public Pattern { impl_binding_ = binding; } + // Return the original generic binding. + auto original() const -> Nonnull { + if (original_.has_value()) + return *original_; + else + return this; + } + // Set the original generic binding. + void set_original(Nonnull orig) { original_ = orig; } + private: std::string name_; Nonnull type_; std::optional> symbolic_identity_; std::optional> impl_binding_; + std::optional> original_; }; // Converts paren_contents to a Pattern, interpreting the parentheses as diff --git a/explorer/data/prelude.carbon b/explorer/data/prelude.carbon index 27c1c0130bf4..9c5112c379f6 100644 --- a/explorer/data/prelude.carbon +++ b/explorer/data/prelude.carbon @@ -39,3 +39,14 @@ impl forall [U1:! Type, U2:! Type, U3:! Type, fn Print(format_str: String) { __intrinsic_print(format_str); } + +class Heap { + fn New[T:! Type, me: Self](x : T) -> T* { + return __intrinsic_new(x); + } + fn Delete[T:! Type, me: Self](p : T*) { + __intrinsic_delete(p); + } +} + +var heap: Heap = {}; diff --git a/explorer/fuzzing/ast_to_proto.cpp b/explorer/fuzzing/ast_to_proto.cpp index 45a5a62f699c..b428c2a95536 100644 --- a/explorer/fuzzing/ast_to_proto.cpp +++ b/explorer/fuzzing/ast_to_proto.cpp @@ -215,6 +215,12 @@ static auto ExpressionToProto(const Expression& expression) case IntrinsicExpression::Intrinsic::Print: intrinsic_proto->set_intrinsic(Fuzzing::IntrinsicExpression::Print); break; + case IntrinsicExpression::Intrinsic::Alloc: + intrinsic_proto->set_intrinsic(Fuzzing::IntrinsicExpression::Alloc); + break; + case IntrinsicExpression::Intrinsic::Dealloc: + intrinsic_proto->set_intrinsic(Fuzzing::IntrinsicExpression::Dealloc); + break; } *intrinsic_proto->mutable_argument() = TupleLiteralExpressionToProto(intrinsic.args()); diff --git a/explorer/interpreter/heap.cpp b/explorer/interpreter/heap.cpp index 08e776d8db76..4d2c30726d09 100644 --- a/explorer/interpreter/heap.cpp +++ b/explorer/interpreter/heap.cpp @@ -56,6 +56,8 @@ void Heap::Deallocate(AllocationId allocation) { } } +void Heap::Deallocate(const Address& a) { Deallocate(a.allocation_); } + void Heap::Print(llvm::raw_ostream& out) const { llvm::ListSeparator sep; for (size_t i = 0; i < values_.size(); ++i) { diff --git a/explorer/interpreter/heap.h b/explorer/interpreter/heap.h index aab3b9d22009..bed2bb5a2d15 100644 --- a/explorer/interpreter/heap.h +++ b/explorer/interpreter/heap.h @@ -40,6 +40,7 @@ class Heap : public HeapAllocationInterface { // Marks this allocation, and all of its sub-objects, as dead. void Deallocate(AllocationId allocation) override; + void Deallocate(const Address& a); // Print all the values on the heap to the stream `out`. void Print(llvm::raw_ostream& out) const; diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index baa918991c36..2bf6a26acf80 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -661,20 +661,28 @@ auto Interpreter::CallFunction(const CallExpression& call, call.source_loc())); RuntimeScope method_scope(&heap_); BindingMap generic_args; + // Bind the receiver to the `me` parameter. CARBON_CHECK(PatternMatch(&method.me_pattern().value(), m.receiver(), call.source_loc(), &method_scope, generic_args, trace_stream_, this->arena_)); + // Bind the arguments to the parameters. CARBON_CHECK(PatternMatch(&method.param_pattern().value(), converted_args, call.source_loc(), &method_scope, generic_args, trace_stream_, this->arena_)); // Bring the class type arguments into scope. for (const auto& [bind, val] : m.type_args()) { - method_scope.Initialize(bind, val); + method_scope.Initialize(bind->original(), val); + } + // Bring the deduced type arguments into scope. + for (const auto& [bind, val] : call.deduced_args()) { + method_scope.Initialize(bind->original(), val); } - // Bring the impl witness tables into scope. + for (const auto& [impl_bind, witness] : witnesses) { + method_scope.Initialize(impl_bind->original(), witness); + } for (const auto& [impl_bind, witness] : m.witnesses()) { - method_scope.Initialize(impl_bind, witness); + method_scope.Initialize(impl_bind->original(), witness); } CARBON_CHECK(method.body().has_value()) << "Calling a method that's missing a body"; @@ -1011,6 +1019,18 @@ auto Interpreter::StepExp() -> ErrorOr { llvm::outs() << cast(*args.elements()[0]).value(); return todo_.FinishAction(TupleValue::Empty()); } + case IntrinsicExpression::Intrinsic::Alloc: { + const auto& args = cast(*act.results()[0]); + CARBON_CHECK(args.elements().size() == 1); + Address addr(heap_.AllocateValue(args.elements()[0])); + return todo_.FinishAction(arena_->New(addr)); + } + case IntrinsicExpression::Intrinsic::Dealloc: { + const auto& args = cast(*act.results()[0]); + CARBON_CHECK(args.elements().size() == 1); + heap_.Deallocate(cast(args.elements()[0])->address()); + return todo_.FinishAction(TupleValue::Empty()); + } } } case ExpressionKind::IntTypeLiteral: { @@ -1419,7 +1439,11 @@ auto Interpreter::StepDeclaration() -> ErrorOr { return todo_.Spawn( std::make_unique(&var_decl.initializer())); } else { - todo_.Initialize(&var_decl.binding(), act.results()[0]); + CARBON_ASSIGN_OR_RETURN( + Nonnull v, + Convert(act.results()[0], &var_decl.binding().static_type(), + var_decl.source_loc())); + todo_.Initialize(&var_decl.binding(), v); return todo_.FinishAction(); } } else { diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index 08cf46106c1d..078fe2904700 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -508,6 +508,12 @@ auto TypeChecker::ArgumentDeduction( -> ErrorOr { if (trace_stream_) { **trace_stream_ << "deducing " << *param << " from " << *arg << "\n"; + **trace_stream_ << "bindings: "; + llvm::ListSeparator sep; + for (auto binding : bindings_to_deduce) { + **trace_stream_ << sep << *binding; + } + **trace_stream_ << "\n"; } // Handle the case where we can't perform deduction, either because the // parameter is a primitive type or because the parameter and argument have @@ -908,12 +914,58 @@ auto TypeChecker::Substitute( } case Value::Kind::FunctionType: { const auto& fn_type = cast(*type); - auto param = Substitute(dict, &fn_type.parameters()); - auto ret = Substitute(dict, &fn_type.return_type()); - // TODO: Only remove the bindings that are in `dict`; we may still need - // to do deduction. - return arena_->New(param, llvm::None, ret, llvm::None, - llvm::None); + std::map, Nonnull> new_dict( + dict); + // Create new generic parameters and generic bindings + // and add them to new_dict. + std::vector generic_parameters; + std::vector> deduced_bindings; + std::map, Nonnull> + bind_map; // Map old generic bindings to new ones. + for (const FunctionType::GenericParameter& gp : + fn_type.generic_parameters()) { + Nonnull new_type = + Substitute(dict, &gp.binding->static_type()); + Nonnull new_gb = arena_->New( + gp.binding->source_loc(), gp.binding->name(), + (Expression*)&gp.binding->type()); // How to avoid the cast? -jsiek + new_gb->set_original(gp.binding->original()); + new_gb->set_static_type(new_type); + FunctionType::GenericParameter new_gp = {.index = gp.index, + .binding = new_gb}; + generic_parameters.push_back(new_gp); + new_dict[gp.binding] = arena_->New(new_gp.binding); + bind_map[gp.binding] = new_gb; + } + for (Nonnull gb : fn_type.deduced_bindings()) { + Nonnull new_type = Substitute(dict, &gb->static_type()); + Nonnull new_gb = arena_->New( + gb->source_loc(), gb->name(), + (Expression*)&gb->type()); // How to avoid the cast? -jsiek + new_gb->set_original(gb->original()); + new_gb->set_static_type(new_type); + deduced_bindings.push_back(new_gb); + new_dict[gb] = arena_->New(new_gb); + bind_map[gb] = new_gb; + } + // Apply substitution to impl bindings and update their + // `type_var` pointers to the new generic bindings. + std::vector> impl_bindings; + for (auto ib : fn_type.impl_bindings()) { + Nonnull new_ib = + arena_->New(ib->source_loc(), bind_map[ib->type_var()], + Substitute(new_dict, ib->interface())); + new_ib->set_original(ib->original()); + impl_bindings.push_back(new_ib); + } + // Apply substitution to parameter types + auto param = Substitute(new_dict, &fn_type.parameters()); + // Apply substitution to return type + auto ret = Substitute(new_dict, &fn_type.return_type()); + // Create the new FunctionType + Nonnull new_fn_type = arena_->New( + param, generic_parameters, ret, deduced_bindings, impl_bindings); + return new_fn_type; } case Value::Kind::PointerType: { return arena_->New( @@ -925,10 +977,6 @@ auto TypeChecker::Substitute( arena_->New( &class_type.declaration(), SubstituteIntoBindingMap(class_type.type_args())); - if (trace_stream_) { - **trace_stream_ << "substitution: " << class_type << " => " - << *new_class_type << "\n"; - } return new_class_type; } case Value::Kind::InterfaceType: { @@ -936,10 +984,6 @@ auto TypeChecker::Substitute( Nonnull new_iface_type = arena_->New( &iface_type.declaration(), SubstituteIntoBindingMap(iface_type.args())); - if (trace_stream_) { - **trace_stream_ << "substitution: " << iface_type << " => " - << *new_iface_type << "\n"; - } return new_iface_type; } case Value::Kind::ConstraintType: { @@ -1112,6 +1156,8 @@ auto TypeChecker::SatisfyImpls( for (Nonnull impl_binding : impl_bindings) { Nonnull interface = Substitute(deduced_type_args, impl_binding->interface()); + CARBON_CHECK(deduced_type_args.find(impl_binding->type_var()) != + deduced_type_args.end()); CARBON_ASSIGN_OR_RETURN( Nonnull impl, impl_scope.Resolve(interface, @@ -1292,8 +1338,6 @@ auto TypeChecker::TypeCheckExp(Nonnull e, if (trace_stream_) { **trace_stream_ << "checking " << ExpressionKindName(e->kind()) << " " << *e; - **trace_stream_ << "\nconstants: "; - PrintConstants(**trace_stream_); **trace_stream_ << "\n"; } if (e->is_type_checked()) { @@ -1449,10 +1493,12 @@ auto TypeChecker::TypeCheckExp(Nonnull e, if (func_decl->is_method() && func_decl->me_pattern().kind() == PatternKind::AddrPattern) { access.set_is_field_addr_me_method(); - CARBON_RETURN_IF_ERROR( - ExpectType(e->source_loc(), "method access", - &func_decl->me_pattern().static_type(), - &access.object().static_type(), &impl_scope)); + Nonnull me_type = + Substitute(t_class.type_args(), + &func_decl->me_pattern().static_type()); + CARBON_RETURN_IF_ERROR(ExpectType( + e->source_loc(), "method access, receiver type", me_type, + &access.object().static_type(), &impl_scope)); if (access.object().value_category() != ValueCategory::Var) { return CompilationError(e->source_loc()) << "method " << access.member() @@ -1863,6 +1909,12 @@ auto TypeChecker::TypeCheckExp(Nonnull e, switch (call.function().static_type().kind()) { case Value::Kind::FunctionType: { const auto& fun_t = cast(call.function().static_type()); + if (trace_stream_) { + **trace_stream_ + << "checking call to function of type " << fun_t + << "\nwith arguments of type: " << call.argument().static_type() + << "\n"; + } CARBON_RETURN_IF_ERROR(DeduceCallBindings( call, &fun_t.parameters(), fun_t.generic_parameters(), fun_t.deduced_bindings(), fun_t.impl_bindings(), impl_scope)); @@ -1966,6 +2018,28 @@ auto TypeChecker::TypeCheckExp(Nonnull e, e->set_static_type(TupleValue::Empty()); e->set_value_category(ValueCategory::Let); return Success(); + case IntrinsicExpression::Intrinsic::Alloc: { + if (intrinsic_exp.args().fields().size() != 1) { + return CompilationError(e->source_loc()) + << "__intrinsic_new takes 1 argument"; + } + auto arg_type = &intrinsic_exp.args().fields()[0]->static_type(); + e->set_static_type(arena_->New(arg_type)); + e->set_value_category(ValueCategory::Let); + return Success(); + } + case IntrinsicExpression::Intrinsic::Dealloc: { + if (intrinsic_exp.args().fields().size() != 1) { + return CompilationError(e->source_loc()) + << "__intrinsic_new takes 1 argument"; + } + auto arg_type = &intrinsic_exp.args().fields()[0]->static_type(); + CARBON_RETURN_IF_ERROR( + ExpectPointerType(e->source_loc(), "*", arg_type)); + e->set_static_type(TupleValue::Empty()); + e->set_value_category(ValueCategory::Let); + return Success(); + } } } case ExpressionKind::IntTypeLiteral: diff --git a/explorer/interpreter/value.cpp b/explorer/interpreter/value.cpp index fd3c8b485479..c5a06f7c407f 100644 --- a/explorer/interpreter/value.cpp +++ b/explorer/interpreter/value.cpp @@ -175,8 +175,11 @@ static auto SetFieldImpl( return arena->New(elements); } case Value::Kind::NominalClassValue: { - return SetFieldImpl(arena, &cast(*value).inits(), - path_begin, path_end, field_value, source_loc); + const NominalClassValue& object = cast(*value); + CARBON_ASSIGN_OR_RETURN(Nonnull inits, + SetFieldImpl(arena, &object.inits(), path_begin, + path_end, field_value, source_loc)); + return arena->New(&object.type(), inits); } case Value::Kind::TupleValue: { std::vector> elements = @@ -280,13 +283,48 @@ void Value::Print(llvm::raw_ostream& out) const { case Value::Kind::BoolValue: out << (cast(*this).value() ? "true" : "false"); break; - case Value::Kind::FunctionValue: - out << "fun<" << cast(*this).declaration().name() << ">"; + case Value::Kind::FunctionValue: { + const FunctionValue& fun = cast(*this); + out << "fun<" << fun.declaration().name() << ">"; + if (!fun.type_args().empty()) { + out << "["; + llvm::ListSeparator sep; + for (const auto& [ty_var, ty_arg] : fun.type_args()) { + out << sep << *ty_var << "=" << *ty_arg; + } + out << "]"; + } + if (!fun.witnesses().empty()) { + out << "{|"; + llvm::ListSeparator sep; + for (const auto& [impl_bind, witness] : fun.witnesses()) { + out << sep << *witness; + } + out << "|}"; + } break; - case Value::Kind::BoundMethodValue: - out << "bound_method<" - << cast(*this).declaration().name() << ">"; + } + case Value::Kind::BoundMethodValue: { + const BoundMethodValue& method = cast(*this); + out << "bound_method<" << method.declaration().name() << ">"; + if (!method.type_args().empty()) { + out << "["; + llvm::ListSeparator sep; + for (const auto& [ty_var, ty_arg] : method.type_args()) { + out << sep << *ty_var << "=" << *ty_arg; + } + out << "]"; + } + if (!method.witnesses().empty()) { + out << "{|"; + llvm::ListSeparator sep; + for (const auto& [impl_bind, witness] : method.witnesses()) { + out << sep << *witness; + } + out << "|}"; + } break; + } case Value::Kind::PointerValue: out << "ptr<" << cast(*this).address() << ">"; break; @@ -316,14 +354,10 @@ void Value::Print(llvm::raw_ostream& out) const { out << "fn "; if (!fn_type.deduced_bindings().empty()) { out << "["; - unsigned int i = 0; + llvm::ListSeparator sep; for (Nonnull deduced : fn_type.deduced_bindings()) { - if (i != 0) { - out << ", "; - } - out << deduced->name() << ":! " << deduced->type(); - ++i; + out << sep << *deduced; } out << "]"; } diff --git a/explorer/testdata/addr/fail-method-me-type.carbon b/explorer/testdata/addr/fail-method-me-type.carbon index f0e3b095c069..3babe10f6a83 100644 --- a/explorer/testdata/addr/fail-method-me-type.carbon +++ b/explorer/testdata/addr/fail-method-me-type.carbon @@ -32,7 +32,7 @@ class Point { fn Main() -> i32 { var p: Point = Point.Origin(); - // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/addr/fail-method-me-type.carbon:[[@LINE+1]]: type error in method access: 'class Point' is not implicitly convertible to 'class Shape' + // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/addr/fail-method-me-type.carbon:[[@LINE+1]]: type error in method access, receiver type: 'class Point' is not implicitly convertible to 'class Shape' var x: auto = p.GetSetX(42); if (p.x == 42) { return x; diff --git a/explorer/testdata/basic_syntax/trace.carbon b/explorer/testdata/basic_syntax/trace.carbon index 817e14ae195e..5b676cb219a9 100644 --- a/explorer/testdata/basic_syntax/trace.carbon +++ b/explorer/testdata/basic_syntax/trace.carbon @@ -18,10 +18,6 @@ // CHECK: ********** starting execution ********** // CHECK: ********** initializing globals ********** // CHECK: ********** calling main function ********** -// CHECK: { -// CHECK: stack: -// CHECK: memory: -// CHECK: } // CHECK: --- step exp Main() .0. (:0) ---> // CHECK: result: 0 diff --git a/explorer/testdata/generic_function/generic_method.carbon b/explorer/testdata/generic_function/generic_method.carbon new file mode 100644 index 000000000000..9733f9340321 --- /dev/null +++ b/explorer/testdata/generic_function/generic_method.carbon @@ -0,0 +1,45 @@ +// 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 +// +// RUN: %{explorer} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{explorer} %s +// CHECK: result: 0 + +package ExplorerTest api; + +class Cell(T:! Type) { + fn Create(x: T) -> Cell(T) { return { .data = x }; } + + fn Get[me: Self]() -> T { + return me.data; + } + fn Put[addr me: Self*](x: T) { + (*me).data = x; + } + fn CreateOther[me: Self, U:! Type](x: U) -> Cell(U) { + return {.data = x}; + } + var data: T; +} + +class Integer { + var int: i32; +} + +impl Integer as ImplicitAs(Integer) { + fn Convert[me: Self]() -> Integer { return me; } +} + +fn Main() -> i32 { + var i: Integer = {.int = 1}; + var c: Cell(Integer) = Cell(Integer).Create(i); + i = {.int = 0}; + c.Put(i); + var j: Integer = c.Get(); + var d: Cell(Integer) = c.CreateOther(j); + return c.data.int + d.data.int; +} diff --git a/explorer/testdata/impl/generic_method_impl.carbon b/explorer/testdata/impl/generic_method_impl.carbon new file mode 100644 index 000000000000..6533f9b67704 --- /dev/null +++ b/explorer/testdata/impl/generic_method_impl.carbon @@ -0,0 +1,49 @@ +// 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 +// +// RUN: %{explorer} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{explorer} %s +// CHECK: result: 3 + +package ExplorerTest api; + +class Cell(T:! Type) { + fn Create(x: T) -> Cell(T) { return { .data = x }; } + + fn Get[me: Self]() -> T { + return me.data; + } + fn Put[addr me: Self*](x: T) { + (*me).data = x; + } + fn Update[addr me: Self*, U:! ImplicitAs(T)](x: U) { + (*me).data = x; + } + fn CreateOther[me: Self, U:! Type](x: U) -> Cell(U) { + return {.data = x}; + } + var data: T; +} + +class Integer { + var int: i32; +} + +impl Integer as ImplicitAs(Integer) { + fn Convert[me: Self]() -> Integer { return me; } +} + +fn Main() -> i32 { + var i: Integer = {.int = 1}; + var c: Cell(Integer) = Cell(Integer).Create(i); // c contains 1 + i = {.int = 2}; + var j: Integer = c.Get(); // j == 1 + c.Put(i); // c contains 2 + c.Update(j); // c contains 1 + var d: Cell(Integer) = c.CreateOther(i); // d contains 2 + return c.data.int + d.data.int; +} diff --git a/explorer/testdata/pointer/aliasing.carbon b/explorer/testdata/pointer/aliasing.carbon new file mode 100644 index 000000000000..9a86d02b0d1d --- /dev/null +++ b/explorer/testdata/pointer/aliasing.carbon @@ -0,0 +1,21 @@ +// 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 +// +// RUN: %{explorer} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{explorer} %s +// CHECK: result: 0 + +package ExplorerTest api; + +// Demonstrate the aliasing behavior of pointers. + +fn Main() -> i32 { + var p: i32* = heap.New(5); + var q: i32* = p; + *q = 0; + return *p; +} diff --git a/explorer/testdata/pointer/fail_use_after_free.carbon b/explorer/testdata/pointer/fail_use_after_free.carbon new file mode 100644 index 000000000000..1ecffecd840d --- /dev/null +++ b/explorer/testdata/pointer/fail_use_after_free.carbon @@ -0,0 +1,18 @@ +// 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 +// +// RUN: %{not} %{explorer} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{explorer} %s + +package ExplorerTest api; + +fn Main() -> i32 { + var p: i32* = heap.New(5); + heap.Delete(p); + // CHECK: RUNTIME ERROR: {{.*}}/explorer/testdata/pointer/fail_use_after_free.carbon:[[@LINE+1]]: undefined behavior: access to dead value 5 + return *p; +} diff --git a/explorer/testdata/pointer/heap_alloc_lifetime.carbon b/explorer/testdata/pointer/heap_alloc_lifetime.carbon new file mode 100644 index 000000000000..52272b6ad31f --- /dev/null +++ b/explorer/testdata/pointer/heap_alloc_lifetime.carbon @@ -0,0 +1,26 @@ +// 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 +// +// RUN: %{explorer} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{explorer} %s +// CHECK: result: 0 + +package ExplorerTest api; + +// The lifetime of a heap-allocated object is until it is explicitely +// deleted. Its lifetime is unrelated to the procedure call stack. + +fn AllocateInteger() -> i32* { + return heap.New(0); +} + +fn Main() -> i32 { + var p: i32* = AllocateInteger(); + var y: i32 = *p; + heap.Delete(p); + return y; +} diff --git a/explorer/testdata/pointer/intrinsic_new.carbon b/explorer/testdata/pointer/intrinsic_new.carbon new file mode 100644 index 000000000000..7e160c26fe68 --- /dev/null +++ b/explorer/testdata/pointer/intrinsic_new.carbon @@ -0,0 +1,19 @@ +// 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 +// +// RUN: %{explorer} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{explorer} %s +// CHECK: result: 0 + +package ExplorerTest api; + +fn Main() -> i32 { + var p: i32* = __intrinsic_new(0); + var y: i32 = *p; + __intrinsic_delete(p); + return y; +} diff --git a/explorer/testdata/pointer/new_and_delete.carbon b/explorer/testdata/pointer/new_and_delete.carbon new file mode 100644 index 000000000000..29f578076660 --- /dev/null +++ b/explorer/testdata/pointer/new_and_delete.carbon @@ -0,0 +1,19 @@ +// 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 +// +// RUN: %{explorer} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{explorer} %s +// CHECK: result: 0 + +package ExplorerTest api; + +fn Main() -> i32 { + var p: i32* = heap.New(0); + var y: i32 = *p; + heap.Delete(p); + return y; +}