diff --git a/explorer/interpreter/BUILD b/explorer/interpreter/BUILD index dd1a6b13434e..6847b363c283 100644 --- a/explorer/interpreter/BUILD +++ b/explorer/interpreter/BUILD @@ -140,6 +140,7 @@ cc_library( srcs = ["resolve_names.cpp"], hdrs = ["resolve_names.h"], deps = [ + ":stack_space", "//common:check", "//explorer/ast", "//explorer/ast:static_scope", @@ -193,6 +194,7 @@ cc_library( ":dictionary", ":interpreter", ":pattern_analysis", + ":stack_space", ":type_structure", "//common:check", "//common:enum_base", @@ -226,6 +228,17 @@ cc_library( ], ) +cc_library( + name = "stack_space", + srcs = ["stack_space.cpp"], + hdrs = ["stack_space.h"], + deps = [ + "//common:check", + "//common:error", + "@llvm-project//llvm:Support", + ], +) + cc_library( name = "type_structure", srcs = [ diff --git a/explorer/interpreter/resolve_names.cpp b/explorer/interpreter/resolve_names.cpp index de63d3716dac..b400b5958fcc 100644 --- a/explorer/interpreter/resolve_names.cpp +++ b/explorer/interpreter/resolve_names.cpp @@ -11,6 +11,7 @@ #include "explorer/ast/pattern.h" #include "explorer/ast/statement.h" #include "explorer/ast/static_scope.h" +#include "explorer/interpreter/stack_space.h" #include "llvm/ADT/DenseMap.h" #include "llvm/Support/Casting.h" #include "llvm/Support/Error.h" @@ -73,27 +74,43 @@ class NameResolver { // such as a namespace. auto ResolveNames(Expression& expression, const StaticScope& enclosing_scope) -> ErrorOr>; + // For RunWithExtraStack. + auto ResolveNamesImpl(Expression& expression, + const StaticScope& enclosing_scope) + -> ErrorOr>; // Resolve all names within the given where clause by looking them up in the // enclosing scope. auto ResolveNames(WhereClause& clause, const StaticScope& enclosing_scope) -> ErrorOr; + // For RunWithExtraStack. + auto ResolveNamesImpl(WhereClause& clause, const StaticScope& enclosing_scope) + -> ErrorOr; // Resolve all names within the given pattern, extending the given scope with // any introduced names. auto ResolveNames(Pattern& pattern, StaticScope& enclosing_scope) -> ErrorOr; + // For RunWithExtraStack. + auto ResolveNamesImpl(Pattern& pattern, StaticScope& enclosing_scope) + -> ErrorOr; // Resolve all names within the given statement, extending the given scope // with any names introduced by declaration statements. auto ResolveNames(Statement& statement, StaticScope& enclosing_scope) -> ErrorOr; + // For RunWithExtraStack. + auto ResolveNamesImpl(Statement& statement, StaticScope& enclosing_scope) + -> ErrorOr; // Resolve all names within the given declaration, extending the given scope // with the any names introduced by the declaration if they're not already // present. auto ResolveNames(Declaration& declaration, StaticScope& enclosing_scope, ResolveFunctionBodies bodies) -> ErrorOr; + // For RunWithExtraStack. + auto ResolveNamesImpl(Declaration& declaration, StaticScope& enclosing_scope, + ResolveFunctionBodies bodies) -> ErrorOr; auto ResolveMemberNames(llvm::ArrayRef> members, StaticScope& scope, ResolveFunctionBodies bodies) @@ -259,6 +276,13 @@ auto NameResolver::AddExposedNames(const Declaration& declaration, auto NameResolver::ResolveNames(Expression& expression, const StaticScope& enclosing_scope) -> ErrorOr> { + return RunWithExtraStack>>( + [&]() { return ResolveNamesImpl(expression, enclosing_scope); }); +} + +auto NameResolver::ResolveNamesImpl(Expression& expression, + const StaticScope& enclosing_scope) + -> ErrorOr> { switch (expression.kind()) { case ExpressionKind::CallExpression: { auto& call = cast(expression); @@ -439,6 +463,13 @@ auto NameResolver::ResolveNames(Expression& expression, auto NameResolver::ResolveNames(WhereClause& clause, const StaticScope& enclosing_scope) -> ErrorOr { + return RunWithExtraStack>( + [&]() { return ResolveNamesImpl(clause, enclosing_scope); }); +} + +auto NameResolver::ResolveNamesImpl(WhereClause& clause, + const StaticScope& enclosing_scope) + -> ErrorOr { switch (clause.kind()) { case WhereClauseKind::ImplsWhereClause: { auto& impls_clause = cast(clause); @@ -468,6 +499,13 @@ auto NameResolver::ResolveNames(WhereClause& clause, auto NameResolver::ResolveNames(Pattern& pattern, StaticScope& enclosing_scope) -> ErrorOr { + return RunWithExtraStack>( + [&]() { return ResolveNamesImpl(pattern, enclosing_scope); }); +} + +auto NameResolver::ResolveNamesImpl(Pattern& pattern, + StaticScope& enclosing_scope) + -> ErrorOr { switch (pattern.kind()) { case PatternKind::BindingPattern: { auto& binding = cast(pattern); @@ -522,6 +560,13 @@ auto NameResolver::ResolveNames(Pattern& pattern, StaticScope& enclosing_scope) auto NameResolver::ResolveNames(Statement& statement, StaticScope& enclosing_scope) -> ErrorOr { + return RunWithExtraStack>( + [&]() { return ResolveNamesImpl(statement, enclosing_scope); }); +} + +auto NameResolver::ResolveNamesImpl(Statement& statement, + StaticScope& enclosing_scope) + -> ErrorOr { switch (statement.kind()) { case StatementKind::ExpressionStatement: CARBON_RETURN_IF_ERROR(ResolveNames( @@ -659,6 +704,14 @@ auto NameResolver::ResolveNames(Declaration& declaration, StaticScope& enclosing_scope, ResolveFunctionBodies bodies) -> ErrorOr { + return RunWithExtraStack>( + [&]() { return ResolveNamesImpl(declaration, enclosing_scope, bodies); }); +} + +auto NameResolver::ResolveNamesImpl(Declaration& declaration, + StaticScope& enclosing_scope, + ResolveFunctionBodies bodies) + -> ErrorOr { switch (declaration.kind()) { case DeclarationKind::NamespaceDeclaration: { auto& namespace_decl = cast(declaration); @@ -874,20 +927,22 @@ auto NameResolver::ResolveNames(Declaration& declaration, } auto ResolveNames(AST& ast) -> ErrorOr { - NameResolver resolver; + return RunWithExtraStack>([&]() -> ErrorOr { + NameResolver resolver; - StaticScope file_scope; - for (auto* declaration : ast.declarations) { - CARBON_RETURN_IF_ERROR(resolver.AddExposedNames( - *declaration, file_scope, /*allow_qualified_names=*/true)); - } - for (auto* declaration : ast.declarations) { - CARBON_RETURN_IF_ERROR(resolver.ResolveNames( - *declaration, file_scope, - NameResolver::ResolveFunctionBodies::AfterDeclarations)); - } - CARBON_RETURN_IF_ERROR(resolver.ResolveNames(**ast.main_call, file_scope)); - return Success(); + StaticScope file_scope; + for (auto* declaration : ast.declarations) { + CARBON_RETURN_IF_ERROR(resolver.AddExposedNames( + *declaration, file_scope, /*allow_qualified_names=*/true)); + } + for (auto* declaration : ast.declarations) { + CARBON_RETURN_IF_ERROR(resolver.ResolveNames( + *declaration, file_scope, + NameResolver::ResolveFunctionBodies::AfterDeclarations)); + } + CARBON_RETURN_IF_ERROR(resolver.ResolveNames(**ast.main_call, file_scope)); + return Success(); + }); } } // namespace Carbon diff --git a/explorer/interpreter/stack_space.cpp b/explorer/interpreter/stack_space.cpp new file mode 100644 index 000000000000..a1597012f3e2 --- /dev/null +++ b/explorer/interpreter/stack_space.cpp @@ -0,0 +1,44 @@ +// 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/interpreter/stack_space.h" + +#include "common/check.h" +#include "llvm/Support/Compiler.h" +#include "llvm/Support/CrashRecoveryContext.h" + +namespace Carbon::Internal { + +static constexpr int64_t SufficientStack = 256 << 10; +static constexpr int64_t DesiredStackSpace = 8 << 20; + +static LLVM_THREAD_LOCAL intptr_t bottom_of_stack = 0; + +// Returns the current bottom of stack. +static auto GetStackPointer() -> intptr_t { + char char_on_stack = 0; + char* volatile ptr = &char_on_stack; + return reinterpret_cast(ptr); +} + +auto IsStackSpaceNearlyExhausted() -> bool { + if (bottom_of_stack == 0) { + // Not initialized on the thread; always start a new thread. + return true; + } + return std::abs(GetStackPointer() - bottom_of_stack) > + (DesiredStackSpace - SufficientStack); +} + +auto RunWithExtraStackHelper(llvm::function_ref fn) -> void { + llvm::CrashRecoveryContext context; + context.RunSafelyOnThread( + [&] { + bottom_of_stack = GetStackPointer(); + fn(); + }, + DesiredStackSpace); +} + +} // namespace Carbon::Internal diff --git a/explorer/interpreter/stack_space.h b/explorer/interpreter/stack_space.h new file mode 100644 index 000000000000..0d936fd76dd0 --- /dev/null +++ b/explorer/interpreter/stack_space.h @@ -0,0 +1,46 @@ +// 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_INTERPRETER_STACK_SPACE_H_ +#define CARBON_EXPLORER_INTERPRETER_STACK_SPACE_H_ + +#include + +#include "llvm/ADT/STLFunctionalExtras.h" + +namespace Carbon { + +namespace Internal { + +// Returns true if a new thread should be started for more stack space. +auto IsStackSpaceNearlyExhausted() -> bool; + +// Starts a thread to run the function. +auto RunWithExtraStackHelper(llvm::function_ref fn) -> void; + +} // namespace Internal + +// Runs `fn` after ensuring there is a reasonable amount of space left on the +// stack for it to run in. This will run `fn` in a separate thread if there is +// not enough space left on the current stack, or if RunWithExtraStack didn't +// create the current thread. +// +// Usage: +// return RunWithExtraStack([&]() -> ReturnType { +// +// }); +template +auto RunWithExtraStack(llvm::function_ref fn) -> ReturnType { + if (Internal::IsStackSpaceNearlyExhausted()) { + std::optional result; + Internal::RunWithExtraStackHelper([&] { result = fn(); }); + return std::move(*result); + } else { + return fn(); + } +} + +} // namespace Carbon + +#endif // CARBON_EXPLORER_INTERPRETER_STACK_SPACE_H_ diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index 9544f86e65b5..f295d74a0f86 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -2739,10 +2739,17 @@ auto TypeChecker::CheckAddrMeAccess( return Success(); } -// NOLINTNEXTLINE(readability-function-size) auto TypeChecker::TypeCheckExp(Nonnull e, const ImplScope& impl_scope) -> ErrorOr { + return RunWithExtraStack>( + [&]() { return TypeCheckExpImpl(e, impl_scope); }); +} + +// NOLINTNEXTLINE(readability-function-size) +auto TypeChecker::TypeCheckExpImpl(Nonnull e, + const ImplScope& impl_scope) + -> ErrorOr { if (trace_stream_->is_enabled()) { *trace_stream_ << "checking " << e->kind() << " " << *e; *trace_stream_ << "\n"; diff --git a/explorer/interpreter/type_checker.h b/explorer/interpreter/type_checker.h index a7a3f859f1a5..0ff1065a24d5 100644 --- a/explorer/interpreter/type_checker.h +++ b/explorer/interpreter/type_checker.h @@ -25,6 +25,7 @@ #include "explorer/interpreter/impl_scope.h" #include "explorer/interpreter/interpreter.h" #include "explorer/interpreter/matching_impl_set.h" +#include "explorer/interpreter/stack_space.h" #include "llvm/ADT/identity.h" namespace Carbon { @@ -192,6 +193,9 @@ class TypeChecker { // directly used in this function but is passed to InterpExp. auto TypeCheckExp(Nonnull e, const ImplScope& impl_scope) -> ErrorOr; + // For RunWithExtraStack. + auto TypeCheckExpImpl(Nonnull e, const ImplScope& impl_scope) + -> ErrorOr; // Type checks and interprets `type_expression`, and validates it represents a // [concrete] type. diff --git a/explorer/parse_and_execute/BUILD b/explorer/parse_and_execute/BUILD index f28ae3d9361a..db0b6cc7afd4 100644 --- a/explorer/parse_and_execute/BUILD +++ b/explorer/parse_and_execute/BUILD @@ -16,8 +16,19 @@ cc_library( "//common:error", "//explorer/common:trace_stream", "//explorer/interpreter:exec_program", + "//explorer/interpreter:stack_space", "//explorer/syntax", "//explorer/syntax:prelude", "@llvm-project//llvm:Support", ], ) + +cc_test( + name = "parse_and_execute_test", + srcs = ["parse_and_execute_test.cpp"], + deps = [ + ":parse_and_execute", + "//common:gtest_main", + "@com_google_googletest//:gtest", + ], +) diff --git a/explorer/parse_and_execute/parse_and_execute.cpp b/explorer/parse_and_execute/parse_and_execute.cpp index 43fe63109d7e..6ffdeba7e0d4 100644 --- a/explorer/parse_and_execute/parse_and_execute.cpp +++ b/explorer/parse_and_execute/parse_and_execute.cpp @@ -9,6 +9,7 @@ #include "common/check.h" #include "common/error.h" #include "explorer/interpreter/exec_program.h" +#include "explorer/interpreter/stack_space.h" #include "explorer/syntax/parse.h" #include "explorer/syntax/prelude.h" #include "llvm/ADT/ScopeExit.h" @@ -39,38 +40,40 @@ static auto ParseAndExecuteHelper(std::function(Arena*)> parse, Nonnull trace_stream, Nonnull print_stream) -> ErrorOr { - Arena arena; - auto cursor = std::chrono::steady_clock::now(); + return RunWithExtraStack>([&]() -> ErrorOr { + Arena arena; + auto cursor = std::chrono::steady_clock::now(); - ErrorOr parse_result = parse(&arena); - auto print_parse_time = PrintTimingOnExit(trace_stream, "Parse", &cursor); - if (!parse_result.ok()) { - return ErrorBuilder() << "SYNTAX ERROR: " << parse_result.error(); - } + ErrorOr parse_result = parse(&arena); + auto print_parse_time = PrintTimingOnExit(trace_stream, "Parse", &cursor); + if (!parse_result.ok()) { + return ErrorBuilder() << "SYNTAX ERROR: " << parse_result.error(); + } - AddPrelude(prelude_path, &arena, &parse_result->declarations, - &parse_result->num_prelude_declarations); - auto print_prelude_time = - PrintTimingOnExit(trace_stream, "AddPrelude", &cursor); + AddPrelude(prelude_path, &arena, &parse_result->declarations, + &parse_result->num_prelude_declarations); + auto print_prelude_time = + PrintTimingOnExit(trace_stream, "AddPrelude", &cursor); - // Semantically analyze the parsed program. - ErrorOr analyze_result = - AnalyzeProgram(&arena, *parse_result, trace_stream, print_stream); - auto print_analyze_time = - PrintTimingOnExit(trace_stream, "AnalyzeProgram", &cursor); - if (!analyze_result.ok()) { - return ErrorBuilder() << "COMPILATION ERROR: " << analyze_result.error(); - } + // Semantically analyze the parsed program. + ErrorOr analyze_result = + AnalyzeProgram(&arena, *parse_result, trace_stream, print_stream); + auto print_analyze_time = + PrintTimingOnExit(trace_stream, "AnalyzeProgram", &cursor); + if (!analyze_result.ok()) { + return ErrorBuilder() << "COMPILATION ERROR: " << analyze_result.error(); + } - // Run the program. - ErrorOr exec_result = - ExecProgram(&arena, *analyze_result, trace_stream, print_stream); - auto print_exec_time = - PrintTimingOnExit(trace_stream, "ExecProgram", &cursor); - if (!exec_result.ok()) { - return ErrorBuilder() << "RUNTIME ERROR: " << exec_result.error(); - } - return exec_result; + // Run the program. + ErrorOr exec_result = + ExecProgram(&arena, *analyze_result, trace_stream, print_stream); + auto print_exec_time = + PrintTimingOnExit(trace_stream, "ExecProgram", &cursor); + if (!exec_result.ok()) { + return ErrorBuilder() << "RUNTIME ERROR: " << exec_result.error(); + } + return exec_result; + }); } auto ParseAndExecuteFile(const std::string& prelude_path, diff --git a/explorer/parse_and_execute/parse_and_execute_test.cpp b/explorer/parse_and_execute/parse_and_execute_test.cpp new file mode 100644 index 000000000000..88ecc2eff603 --- /dev/null +++ b/explorer/parse_and_execute/parse_and_execute_test.cpp @@ -0,0 +1,42 @@ +// 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/parse_and_execute/parse_and_execute.h" + +#include +#include + +namespace Carbon::Testing { +namespace { + +using ::testing::Eq; + +TEST(ParseAndExecuteTest, Recursion) { + std::string source = R"( + package Test api; + fn Main() -> i32 { + return + )"; + // A high depth that's expected to complete in a few seconds. + static constexpr int Depth = 50000; + for (int i = 0; i < Depth; ++i) { + source += "if true then\n"; + } + source += "1\n"; + for (int i = 0; i < Depth; ++i) { + source += "else 0\n"; + } + source += R"( + ; + } + )"; + auto err = ParseAndExecute("explorer/data/prelude.carbon", source); + ASSERT_FALSE(err.ok()); + EXPECT_THAT(err.error().message(), + Eq("RUNTIME ERROR: overflow:1: stack overflow: too many " + "interpreter actions on stack")); +} + +} // namespace +} // namespace Carbon::Testing