| // Copyright 2017 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/implementation-visitor.h" |
| |
| #include <algorithm> |
| #include <iomanip> |
| #include <string> |
| |
| #include "src/base/optional.h" |
| #include "src/common/globals.h" |
| #include "src/torque/cc-generator.h" |
| #include "src/torque/constants.h" |
| #include "src/torque/csa-generator.h" |
| #include "src/torque/declaration-visitor.h" |
| #include "src/torque/global-context.h" |
| #include "src/torque/parameter-difference.h" |
| #include "src/torque/server-data.h" |
| #include "src/torque/type-inference.h" |
| #include "src/torque/type-visitor.h" |
| #include "src/torque/types.h" |
| |
| namespace v8 { |
| namespace internal { |
| namespace torque { |
| |
| VisitResult ImplementationVisitor::Visit(Expression* expr) { |
| CurrentSourcePosition::Scope scope(expr->pos); |
| switch (expr->kind) { |
| #define ENUM_ITEM(name) \ |
| case AstNode::Kind::k##name: \ |
| return Visit(name::cast(expr)); |
| AST_EXPRESSION_NODE_KIND_LIST(ENUM_ITEM) |
| #undef ENUM_ITEM |
| default: |
| UNREACHABLE(); |
| } |
| } |
| |
| const Type* ImplementationVisitor::Visit(Statement* stmt) { |
| CurrentSourcePosition::Scope scope(stmt->pos); |
| StackScope stack_scope(this); |
| const Type* result; |
| switch (stmt->kind) { |
| #define ENUM_ITEM(name) \ |
| case AstNode::Kind::k##name: \ |
| result = Visit(name::cast(stmt)); \ |
| break; |
| AST_STATEMENT_NODE_KIND_LIST(ENUM_ITEM) |
| #undef ENUM_ITEM |
| default: |
| UNREACHABLE(); |
| } |
| DCHECK_EQ(result == TypeOracle::GetNeverType(), |
| assembler().CurrentBlockIsComplete()); |
| return result; |
| } |
| |
| void ImplementationVisitor::BeginGeneratedFiles() { |
| std::set<SourceId> contains_class_definitions; |
| for (const ClassType* type : TypeOracle::GetClasses()) { |
| if (type->GenerateCppClassDefinitions()) { |
| contains_class_definitions.insert(type->AttributedToFile()); |
| } |
| } |
| |
| for (SourceId file : SourceFileMap::AllSources()) { |
| // Output beginning of CSA .cc file. |
| { |
| std::ostream& out = GlobalContext::GeneratedPerFile(file).csa_ccfile; |
| |
| for (const std::string& include_path : GlobalContext::CppIncludes()) { |
| out << "#include " << StringLiteralQuote(include_path) << "\n"; |
| } |
| |
| for (SourceId file : SourceFileMap::AllSources()) { |
| out << "#include \"torque-generated/" + |
| SourceFileMap::PathFromV8RootWithoutExtension(file) + |
| "-tq-csa.h\"\n"; |
| } |
| out << "\n"; |
| |
| out << "namespace v8 {\n" |
| << "namespace internal {\n" |
| << "\n"; |
| } |
| // Output beginning of CSA .h file. |
| { |
| std::ostream& out = GlobalContext::GeneratedPerFile(file).csa_headerfile; |
| std::string headerDefine = |
| "V8_GEN_TORQUE_GENERATED_" + |
| UnderlinifyPath(SourceFileMap::PathFromV8Root(file)) + "_H_"; |
| out << "#ifndef " << headerDefine << "\n"; |
| out << "#define " << headerDefine << "\n\n"; |
| out << "#include \"src/builtins/torque-csa-header-includes.h\"\n"; |
| out << "\n"; |
| |
| out << "namespace v8 {\n" |
| << "namespace internal {\n" |
| << "\n"; |
| } |
| // Output beginning of class definition .cc file. |
| { |
| auto& streams = GlobalContext::GeneratedPerFile(file); |
| std::ostream& out = streams.class_definition_ccfile; |
| if (contains_class_definitions.count(file) != 0) { |
| out << "#include \"" |
| << SourceFileMap::PathFromV8RootWithoutExtension(file) |
| << "-inl.h\"\n\n"; |
| out << "#include \"torque-generated/class-verifiers.h\"\n"; |
| out << "#include \"src/objects/instance-type-inl.h\"\n\n"; |
| } |
| |
| out << "namespace v8 {\n"; |
| out << "namespace internal {\n"; |
| } |
| } |
| } |
| |
| void ImplementationVisitor::EndGeneratedFiles() { |
| for (SourceId file : SourceFileMap::AllSources()) { |
| { |
| std::ostream& out = GlobalContext::GeneratedPerFile(file).csa_ccfile; |
| |
| out << "} // namespace internal\n" |
| << "} // namespace v8\n" |
| << "\n"; |
| } |
| { |
| std::ostream& out = GlobalContext::GeneratedPerFile(file).csa_headerfile; |
| |
| std::string headerDefine = |
| "V8_GEN_TORQUE_GENERATED_" + |
| UnderlinifyPath(SourceFileMap::PathFromV8Root(file)) + "_H_"; |
| |
| out << "} // namespace internal\n" |
| << "} // namespace v8\n" |
| << "\n"; |
| out << "#endif // " << headerDefine << "\n"; |
| } |
| { |
| std::ostream& out = |
| GlobalContext::GeneratedPerFile(file).class_definition_ccfile; |
| |
| out << "} // namespace v8\n"; |
| out << "} // namespace internal\n"; |
| } |
| } |
| } |
| |
| void ImplementationVisitor::BeginRuntimeMacrosFile() { |
| std::ostream& source = runtime_macros_cc_; |
| std::ostream& header = runtime_macros_h_; |
| |
| source << "#include \"torque-generated/runtime-macros.h\"\n\n"; |
| source << "#include \"src/torque/runtime-macro-shims.h\"\n"; |
| for (const std::string& include_path : GlobalContext::CppIncludes()) { |
| source << "#include " << StringLiteralQuote(include_path) << "\n"; |
| } |
| source << "\n"; |
| |
| source << "namespace v8 {\n" |
| << "namespace internal {\n" |
| << "\n"; |
| |
| const char* kHeaderDefine = "V8_GEN_TORQUE_GENERATED_RUNTIME_MACROS_H_"; |
| header << "#ifndef " << kHeaderDefine << "\n"; |
| header << "#define " << kHeaderDefine << "\n\n"; |
| header << "#include \"src/builtins/torque-csa-header-includes.h\"\n"; |
| header << "\n"; |
| |
| header << "namespace v8 {\n" |
| << "namespace internal {\n" |
| << "\n"; |
| } |
| |
| void ImplementationVisitor::EndRuntimeMacrosFile() { |
| std::ostream& source = runtime_macros_cc_; |
| std::ostream& header = runtime_macros_h_; |
| |
| source << "} // namespace internal\n" |
| << "} // namespace v8\n" |
| << "\n"; |
| |
| header << "\n} // namespace internal\n" |
| << "} // namespace v8\n" |
| << "\n"; |
| header << "#endif // V8_GEN_TORQUE_GENERATED_RUNTIME_MACROS_H_\n"; |
| } |
| |
| void ImplementationVisitor::Visit(NamespaceConstant* decl) { |
| Signature signature{{}, base::nullopt, {{}, false}, 0, decl->type(), |
| {}, false}; |
| |
| BindingsManagersScope bindings_managers_scope; |
| |
| csa_headerfile() << " "; |
| GenerateFunctionDeclaration(csa_headerfile(), "", decl->external_name(), |
| signature, {}); |
| csa_headerfile() << ";\n"; |
| |
| GenerateFunctionDeclaration(csa_ccfile(), "", decl->external_name(), |
| signature, {}); |
| csa_ccfile() << " {\n"; |
| csa_ccfile() << " compiler::CodeAssembler ca_(state_);\n"; |
| |
| DCHECK(!signature.return_type->IsVoidOrNever()); |
| |
| assembler_ = CfgAssembler(Stack<const Type*>{}); |
| |
| VisitResult expression_result = Visit(decl->body()); |
| VisitResult return_result = |
| GenerateImplicitConvert(signature.return_type, expression_result); |
| |
| CSAGenerator csa_generator{assembler().Result(), csa_ccfile()}; |
| Stack<std::string> values = *csa_generator.EmitGraph(Stack<std::string>{}); |
| |
| assembler_ = base::nullopt; |
| |
| csa_ccfile() << " return "; |
| CSAGenerator::EmitCSAValue(return_result, values, csa_ccfile()); |
| csa_ccfile() << ";\n"; |
| csa_ccfile() << "}\n\n"; |
| } |
| |
| void ImplementationVisitor::Visit(TypeAlias* alias) { |
| if (alias->IsRedeclaration()) return; |
| if (const ClassType* class_type = ClassType::DynamicCast(alias->type())) { |
| if (class_type->IsExtern() && !class_type->nspace()->IsDefaultNamespace()) { |
| Error( |
| "extern classes are currently only supported in the default " |
| "namespace"); |
| } |
| } |
| } |
| |
| VisitResult ImplementationVisitor::InlineMacro( |
| Macro* macro, base::Optional<LocationReference> this_reference, |
| const std::vector<VisitResult>& arguments, |
| const std::vector<Block*> label_blocks) { |
| CurrentScope::Scope current_scope(macro); |
| BindingsManagersScope bindings_managers_scope; |
| CurrentCallable::Scope current_callable(macro); |
| CurrentReturnValue::Scope current_return_value; |
| const Signature& signature = macro->signature(); |
| const Type* return_type = macro->signature().return_type; |
| bool can_return = return_type != TypeOracle::GetNeverType(); |
| |
| BlockBindings<LocalValue> parameter_bindings(&ValueBindingsManager::Get()); |
| BlockBindings<LocalLabel> label_bindings(&LabelBindingsManager::Get()); |
| DCHECK_EQ(macro->signature().parameter_names.size(), |
| arguments.size() + (this_reference ? 1 : 0)); |
| DCHECK_EQ(this_reference.has_value(), macro->IsMethod()); |
| |
| // Bind the this for methods. Methods that modify a struct-type "this" must |
| // only be called if the this is in a variable, in which case the |
| // LocalValue is non-const. Otherwise, the LocalValue used for the parameter |
| // binding is const, and thus read-only, which will cause errors if |
| // modified, e.g. when called by a struct method that sets the structs |
| // fields. This prevents using temporary struct values for anything other |
| // than read operations. |
| if (this_reference) { |
| DCHECK(macro->IsMethod()); |
| parameter_bindings.Add(kThisParameterName, LocalValue{*this_reference}, |
| true); |
| } |
| |
| size_t i = 0; |
| for (auto arg : arguments) { |
| if (this_reference && i == signature.implicit_count) i++; |
| const bool mark_as_used = signature.implicit_count > i; |
| const Identifier* name = macro->parameter_names()[i++]; |
| parameter_bindings.Add(name, |
| LocalValue{LocationReference::Temporary( |
| arg, "parameter " + name->value)}, |
| mark_as_used); |
| } |
| |
| DCHECK_EQ(label_blocks.size(), signature.labels.size()); |
| for (size_t i = 0; i < signature.labels.size(); ++i) { |
| const LabelDeclaration& label_info = signature.labels[i]; |
| label_bindings.Add(label_info.name, |
| LocalLabel{label_blocks[i], label_info.types}); |
| } |
| |
| Block* macro_end; |
| base::Optional<Binding<LocalLabel>> macro_end_binding; |
| if (can_return) { |
| Stack<const Type*> stack = assembler().CurrentStack(); |
| std::vector<const Type*> lowered_return_types = LowerType(return_type); |
| stack.PushMany(lowered_return_types); |
| if (!return_type->IsConstexpr()) { |
| SetReturnValue(VisitResult(return_type, |
| stack.TopRange(lowered_return_types.size()))); |
| } |
| // The stack copy used to initialize the _macro_end block is only used |
| // as a template for the actual gotos generated by return statements. It |
| // doesn't correspond to any real return values, and thus shouldn't contain |
| // top types, because these would pollute actual return value types that get |
| // unioned with them for return statements, erroneously forcing them to top. |
| for (auto i = stack.begin(); i != stack.end(); ++i) { |
| if ((*i)->IsTopType()) { |
| *i = TopType::cast(*i)->source_type(); |
| } |
| } |
| macro_end = assembler().NewBlock(std::move(stack)); |
| macro_end_binding.emplace(&LabelBindingsManager::Get(), kMacroEndLabelName, |
| LocalLabel{macro_end, {return_type}}); |
| } else { |
| SetReturnValue(VisitResult::NeverResult()); |
| } |
| |
| const Type* result = Visit(*macro->body()); |
| |
| if (result->IsNever()) { |
| if (!return_type->IsNever() && !macro->HasReturns()) { |
| std::stringstream s; |
| s << "macro " << macro->ReadableName() |
| << " that never returns must have return type never"; |
| ReportError(s.str()); |
| } |
| } else { |
| if (return_type->IsNever()) { |
| std::stringstream s; |
| s << "macro " << macro->ReadableName() |
| << " has implicit return at end of its declartion but return type " |
| "never"; |
| ReportError(s.str()); |
| } else if (!macro->signature().return_type->IsVoid()) { |
| std::stringstream s; |
| s << "macro " << macro->ReadableName() |
| << " expects to return a value but doesn't on all paths"; |
| ReportError(s.str()); |
| } |
| } |
| if (!result->IsNever()) { |
| assembler().Goto(macro_end); |
| } |
| |
| if (macro->HasReturns() || !result->IsNever()) { |
| assembler().Bind(macro_end); |
| } |
| |
| return GetAndClearReturnValue(); |
| } |
| |
| void ImplementationVisitor::VisitMacroCommon(Macro* macro) { |
| CurrentCallable::Scope current_callable(macro); |
| const Signature& signature = macro->signature(); |
| const Type* return_type = macro->signature().return_type; |
| bool can_return = return_type != TypeOracle::GetNeverType(); |
| bool has_return_value = |
| can_return && return_type != TypeOracle::GetVoidType(); |
| |
| GenerateMacroFunctionDeclaration(csa_headerfile(), macro); |
| csa_headerfile() << ";\n"; |
| |
| GenerateMacroFunctionDeclaration(csa_ccfile(), macro); |
| csa_ccfile() << " {\n"; |
| |
| if (output_type_ == OutputType::kCC) { |
| // For now, generated C++ is only for field offset computations. If we ever |
| // generate C++ code that can allocate, then it should be handlified. |
| csa_ccfile() << " DisallowHeapAllocation no_gc;\n"; |
| } else { |
| csa_ccfile() << " compiler::CodeAssembler ca_(state_);\n"; |
| csa_ccfile() |
| << " compiler::CodeAssembler::SourcePositionScope pos_scope(&ca_);\n"; |
| } |
| |
| Stack<std::string> lowered_parameters; |
| Stack<const Type*> lowered_parameter_types; |
| |
| std::vector<VisitResult> arguments; |
| |
| base::Optional<LocationReference> this_reference; |
| if (Method* method = Method::DynamicCast(macro)) { |
| const Type* this_type = method->aggregate_type(); |
| LowerParameter(this_type, ExternalParameterName(kThisParameterName), |
| &lowered_parameters); |
| StackRange range = lowered_parameter_types.PushMany(LowerType(this_type)); |
| VisitResult this_result = VisitResult(this_type, range); |
| // For classes, mark 'this' as a temporary to prevent assignment to it. |
| // Note that using a VariableAccess for non-class types is technically |
| // incorrect because changes to the 'this' variable do not get reflected |
| // to the caller. Therefore struct methods should always be inlined and a |
| // C++ version should never be generated, since it would be incorrect. |
| // However, in order to be able to type- and semantics-check even unused |
| // struct methods, set the this_reference to be the local variable copy of |
| // the passed-in this, which allows the visitor to at least find and report |
| // errors. |
| this_reference = |
| (this_type->IsClassType()) |
| ? LocationReference::Temporary(this_result, "this parameter") |
| : LocationReference::VariableAccess(this_result); |
| } |
| |
| for (size_t i = 0; i < macro->signature().parameter_names.size(); ++i) { |
| if (this_reference && i == macro->signature().implicit_count) continue; |
| const std::string& name = macro->parameter_names()[i]->value; |
| std::string external_name = ExternalParameterName(name); |
| const Type* type = macro->signature().types()[i]; |
| |
| if (type->IsConstexpr()) { |
| arguments.push_back(VisitResult(type, external_name)); |
| } else { |
| LowerParameter(type, external_name, &lowered_parameters); |
| StackRange range = lowered_parameter_types.PushMany(LowerType(type)); |
| arguments.push_back(VisitResult(type, range)); |
| } |
| } |
| |
| DCHECK_EQ(lowered_parameters.Size(), lowered_parameter_types.Size()); |
| assembler_ = CfgAssembler(lowered_parameter_types); |
| |
| std::vector<Block*> label_blocks; |
| for (const LabelDeclaration& label_info : signature.labels) { |
| Stack<const Type*> label_input_stack; |
| for (const Type* type : label_info.types) { |
| label_input_stack.PushMany(LowerType(type)); |
| } |
| Block* block = assembler().NewBlock(std::move(label_input_stack)); |
| label_blocks.push_back(block); |
| } |
| |
| VisitResult return_value = |
| InlineMacro(macro, this_reference, arguments, label_blocks); |
| Block* end = assembler().NewBlock(); |
| if (return_type != TypeOracle::GetNeverType()) { |
| assembler().Goto(end); |
| } |
| |
| for (size_t i = 0; i < label_blocks.size(); ++i) { |
| Block* label_block = label_blocks[i]; |
| const LabelDeclaration& label_info = signature.labels[i]; |
| assembler().Bind(label_block); |
| std::vector<std::string> label_parameter_variables; |
| for (size_t i = 0; i < label_info.types.size(); ++i) { |
| LowerLabelParameter(label_info.types[i], |
| ExternalLabelParameterName(label_info.name->value, i), |
| &label_parameter_variables); |
| } |
| assembler().Emit(GotoExternalInstruction{ |
| ExternalLabelName(label_info.name->value), label_parameter_variables}); |
| } |
| |
| if (return_type != TypeOracle::GetNeverType()) { |
| assembler().Bind(end); |
| } |
| |
| base::Optional<Stack<std::string>> values; |
| if (output_type_ == OutputType::kCC) { |
| CCGenerator cc_generator{assembler().Result(), csa_ccfile()}; |
| values = cc_generator.EmitGraph(lowered_parameters); |
| } else { |
| CSAGenerator csa_generator{assembler().Result(), csa_ccfile()}; |
| values = csa_generator.EmitGraph(lowered_parameters); |
| } |
| |
| assembler_ = base::nullopt; |
| |
| if (has_return_value) { |
| csa_ccfile() << " return "; |
| if (output_type_ == OutputType::kCC) { |
| CCGenerator::EmitCCValue(return_value, *values, csa_ccfile()); |
| } else { |
| CSAGenerator::EmitCSAValue(return_value, *values, csa_ccfile()); |
| } |
| csa_ccfile() << ";\n"; |
| } |
| csa_ccfile() << "}\n\n"; |
| } |
| |
| void ImplementationVisitor::Visit(TorqueMacro* macro) { |
| VisitMacroCommon(macro); |
| } |
| |
| void ImplementationVisitor::Visit(Method* method) { |
| DCHECK(!method->IsExternal()); |
| VisitMacroCommon(method); |
| } |
| |
| namespace { |
| |
| std::string AddParameter(size_t i, Builtin* builtin, |
| Stack<std::string>* parameters, |
| Stack<const Type*>* parameter_types, |
| BlockBindings<LocalValue>* parameter_bindings, |
| bool mark_as_used) { |
| const Identifier* name = builtin->signature().parameter_names[i]; |
| const Type* type = builtin->signature().types()[i]; |
| std::string external_name = "parameter" + std::to_string(i); |
| parameters->Push(external_name); |
| StackRange range = parameter_types->PushMany(LowerType(type)); |
| parameter_bindings->Add( |
| name, |
| LocalValue{LocationReference::Temporary(VisitResult(type, range), |
| "parameter " + name->value)}, |
| mark_as_used); |
| return external_name; |
| } |
| |
| } // namespace |
| |
| void ImplementationVisitor::Visit(Builtin* builtin) { |
| if (builtin->IsExternal()) return; |
| CurrentScope::Scope current_scope(builtin); |
| CurrentCallable::Scope current_callable(builtin); |
| CurrentReturnValue::Scope current_return_value; |
| |
| const std::string& name = builtin->ExternalName(); |
| const Signature& signature = builtin->signature(); |
| csa_ccfile() << "TF_BUILTIN(" << name << ", CodeStubAssembler) {\n" |
| << " compiler::CodeAssemblerState* state_ = state();" |
| << " compiler::CodeAssembler ca_(state());\n"; |
| |
| Stack<const Type*> parameter_types; |
| Stack<std::string> parameters; |
| |
| BindingsManagersScope bindings_managers_scope; |
| |
| BlockBindings<LocalValue> parameter_bindings(&ValueBindingsManager::Get()); |
| |
| if (builtin->IsVarArgsJavaScript() || builtin->IsFixedArgsJavaScript()) { |
| if (builtin->IsVarArgsJavaScript()) { |
| DCHECK(signature.parameter_types.var_args); |
| if (signature.ExplicitCount() > 0) { |
| Error("Cannot mix explicit parameters with varargs.") |
| .Position(signature.parameter_names[signature.implicit_count]->pos); |
| } |
| |
| csa_ccfile() << " TNode<Word32T> argc = UncheckedParameter<Word32T>(" |
| << "Descriptor::kJSActualArgumentsCount);\n"; |
| csa_ccfile() << " TNode<IntPtrT> " |
| "arguments_length(ChangeInt32ToIntPtr(UncheckedCast<" |
| "Int32T>(argc)));\n"; |
| csa_ccfile() << " TNode<RawPtrT> arguments_frame = " |
| "UncheckedCast<RawPtrT>(LoadFramePointer());\n"; |
| csa_ccfile() << " TorqueStructArguments " |
| "torque_arguments(GetFrameArguments(arguments_frame, " |
| "arguments_length));\n"; |
| csa_ccfile() |
| << " CodeStubArguments arguments(this, torque_arguments);\n"; |
| |
| parameters.Push("torque_arguments.frame"); |
| parameters.Push("torque_arguments.base"); |
| parameters.Push("torque_arguments.length"); |
| const Type* arguments_type = TypeOracle::GetArgumentsType(); |
| StackRange range = parameter_types.PushMany(LowerType(arguments_type)); |
| parameter_bindings.Add(*signature.arguments_variable, |
| LocalValue{LocationReference::Temporary( |
| VisitResult(arguments_type, range), |
| "parameter " + *signature.arguments_variable)}, |
| true); |
| } |
| |
| for (size_t i = 0; i < signature.implicit_count; ++i) { |
| const std::string& param_name = signature.parameter_names[i]->value; |
| SourcePosition param_pos = signature.parameter_names[i]->pos; |
| std::string generated_name = AddParameter( |
| i, builtin, ¶meters, ¶meter_types, ¶meter_bindings, true); |
| const Type* actual_type = signature.parameter_types.types[i]; |
| std::vector<const Type*> expected_types; |
| if (param_name == "context") { |
| csa_ccfile() << " TNode<NativeContext> " << generated_name |
| << " = UncheckedParameter<NativeContext>(" |
| << "Descriptor::kContext);\n"; |
| csa_ccfile() << " USE(" << generated_name << ");\n"; |
| expected_types = {TypeOracle::GetNativeContextType(), |
| TypeOracle::GetContextType()}; |
| } else if (param_name == "receiver") { |
| csa_ccfile() |
| << " TNode<Object> " << generated_name << " = " |
| << (builtin->IsVarArgsJavaScript() |
| ? "arguments.GetReceiver()" |
| : "UncheckedParameter<Object>(Descriptor::kReceiver)") |
| << ";\n"; |
| csa_ccfile() << "USE(" << generated_name << ");\n"; |
| expected_types = {TypeOracle::GetJSAnyType()}; |
| } else if (param_name == "newTarget") { |
| csa_ccfile() << " TNode<Object> " << generated_name |
| << " = UncheckedParameter<Object>(" |
| << "Descriptor::kJSNewTarget);\n"; |
| csa_ccfile() << "USE(" << generated_name << ");\n"; |
| expected_types = {TypeOracle::GetJSAnyType()}; |
| } else if (param_name == "target") { |
| csa_ccfile() << " TNode<JSFunction> " << generated_name |
| << " = UncheckedParameter<JSFunction>(" |
| << "Descriptor::kJSTarget);\n"; |
| csa_ccfile() << "USE(" << generated_name << ");\n"; |
| expected_types = {TypeOracle::GetJSFunctionType()}; |
| } else { |
| Error( |
| "Unexpected implicit parameter \"", param_name, |
| "\" for JavaScript calling convention, " |
| "expected \"context\", \"receiver\", \"target\", or \"newTarget\"") |
| .Position(param_pos); |
| expected_types = {actual_type}; |
| } |
| if (std::find(expected_types.begin(), expected_types.end(), |
| actual_type) == expected_types.end()) { |
| Error("According to JavaScript calling convention, expected parameter ", |
| param_name, " to have type ", PrintList(expected_types, " or "), |
| " but found type ", *actual_type) |
| .Position(param_pos); |
| } |
| } |
| |
| for (size_t i = signature.implicit_count; |
| i < signature.parameter_names.size(); ++i) { |
| const std::string& parameter_name = signature.parameter_names[i]->value; |
| const Type* type = signature.types()[i]; |
| const bool mark_as_used = signature.implicit_count > i; |
| std::string var = AddParameter(i, builtin, ¶meters, ¶meter_types, |
| ¶meter_bindings, mark_as_used); |
| csa_ccfile() << " " << type->GetGeneratedTypeName() << " " << var |
| << " = " |
| << "UncheckedParameter<" << type->GetGeneratedTNodeTypeName() |
| << ">(Descriptor::k" << CamelifyString(parameter_name) |
| << ");\n"; |
| csa_ccfile() << " USE(" << var << ");\n"; |
| } |
| |
| } else { |
| DCHECK(builtin->IsStub()); |
| |
| bool has_context_parameter = signature.HasContextParameter(); |
| for (size_t i = 0; i < signature.parameter_names.size(); ++i) { |
| const Type* type = signature.types()[i]; |
| const bool mark_as_used = signature.implicit_count > i; |
| std::string var = AddParameter(i, builtin, ¶meters, ¶meter_types, |
| ¶meter_bindings, mark_as_used); |
| csa_ccfile() << " " << type->GetGeneratedTypeName() << " " << var |
| << " = " |
| << "UncheckedParameter<" << type->GetGeneratedTNodeTypeName() |
| << ">("; |
| if (i == 0 && has_context_parameter) { |
| csa_ccfile() << "Descriptor::kContext"; |
| } else { |
| csa_ccfile() << "Descriptor::ParameterIndex<" |
| << (has_context_parameter ? i - 1 : i) << ">()"; |
| } |
| csa_ccfile() << ");\n"; |
| csa_ccfile() << " USE(" << var << ");\n"; |
| } |
| } |
| assembler_ = CfgAssembler(parameter_types); |
| const Type* body_result = Visit(*builtin->body()); |
| if (body_result != TypeOracle::GetNeverType()) { |
| ReportError("control reaches end of builtin, expected return of a value"); |
| } |
| CSAGenerator csa_generator{assembler().Result(), csa_ccfile(), |
| builtin->kind()}; |
| csa_generator.EmitGraph(parameters); |
| assembler_ = base::nullopt; |
| csa_ccfile() << "}\n\n"; |
| } |
| |
| const Type* ImplementationVisitor::Visit(VarDeclarationStatement* stmt) { |
| BlockBindings<LocalValue> block_bindings(&ValueBindingsManager::Get()); |
| return Visit(stmt, &block_bindings); |
| } |
| |
| const Type* ImplementationVisitor::Visit( |
| VarDeclarationStatement* stmt, BlockBindings<LocalValue>* block_bindings) { |
| // const qualified variables are required to be initialized properly. |
| if (stmt->const_qualified && !stmt->initializer) { |
| ReportError("local constant \"", stmt->name, "\" is not initialized."); |
| } |
| |
| base::Optional<const Type*> type; |
| if (stmt->type) { |
| type = TypeVisitor::ComputeType(*stmt->type); |
| } |
| base::Optional<VisitResult> init_result; |
| if (stmt->initializer) { |
| StackScope scope(this); |
| init_result = Visit(*stmt->initializer); |
| if (type) { |
| init_result = GenerateImplicitConvert(*type, *init_result); |
| } |
| type = init_result->type(); |
| if ((*type)->IsConstexpr() && !stmt->const_qualified) { |
| Error("Use 'const' instead of 'let' for variable '", stmt->name->value, |
| "' of constexpr type '", (*type)->ToString(), "'.") |
| .Position(stmt->name->pos) |
| .Throw(); |
| } |
| init_result = scope.Yield(*init_result); |
| } else { |
| DCHECK(type.has_value()); |
| if ((*type)->IsConstexpr()) { |
| ReportError("constexpr variables need an initializer"); |
| } |
| TypeVector lowered_types = LowerType(*type); |
| for (const Type* type : lowered_types) { |
| assembler().Emit(PushUninitializedInstruction{TypeOracle::GetTopType( |
| "uninitialized variable '" + stmt->name->value + "' of type " + |
| type->ToString() + " originally defined at " + |
| PositionAsString(stmt->pos), |
| type)}); |
| } |
| init_result = |
| VisitResult(*type, assembler().TopRange(lowered_types.size())); |
| } |
| LocationReference ref = stmt->const_qualified |
| ? LocationReference::Temporary( |
| *init_result, "const " + stmt->name->value) |
| : LocationReference::VariableAccess(*init_result); |
| block_bindings->Add(stmt->name, LocalValue{std::move(ref)}); |
| return TypeOracle::GetVoidType(); |
| } |
| |
| const Type* ImplementationVisitor::Visit(TailCallStatement* stmt) { |
| return Visit(stmt->call, true).type(); |
| } |
| |
| VisitResult ImplementationVisitor::Visit(ConditionalExpression* expr) { |
| Block* true_block = assembler().NewBlock(assembler().CurrentStack()); |
| Block* false_block = assembler().NewBlock(assembler().CurrentStack()); |
| Block* done_block = assembler().NewBlock(); |
| Block* true_conversion_block = assembler().NewBlock(); |
| GenerateExpressionBranch(expr->condition, true_block, false_block); |
| |
| VisitResult left; |
| VisitResult right; |
| |
| { |
| // The code for both paths of the conditional need to be generated first |
| // before evaluating the conditional expression because the common type of |
| // the result of both the true and false of the condition needs to be known |
| // to convert both branches to a common type. |
| assembler().Bind(true_block); |
| StackScope left_scope(this); |
| left = Visit(expr->if_true); |
| assembler().Goto(true_conversion_block); |
| |
| const Type* common_type; |
| { |
| assembler().Bind(false_block); |
| StackScope right_scope(this); |
| right = Visit(expr->if_false); |
| common_type = GetCommonType(left.type(), right.type()); |
| right = right_scope.Yield(GenerateImplicitConvert(common_type, right)); |
| assembler().Goto(done_block); |
| } |
| |
| assembler().Bind(true_conversion_block); |
| left = left_scope.Yield(GenerateImplicitConvert(common_type, left)); |
| assembler().Goto(done_block); |
| } |
| |
| assembler().Bind(done_block); |
| CHECK_EQ(left, right); |
| return left; |
| } |
| |
| VisitResult ImplementationVisitor::Visit(LogicalOrExpression* expr) { |
| StackScope outer_scope(this); |
| VisitResult left_result = Visit(expr->left); |
| |
| if (left_result.type()->IsConstexprBool()) { |
| VisitResult right_result = Visit(expr->right); |
| if (!right_result.type()->IsConstexprBool()) { |
| ReportError( |
| "expected type constexpr bool on right-hand side of operator " |
| "||"); |
| } |
| return VisitResult(TypeOracle::GetConstexprBoolType(), |
| std::string("(") + left_result.constexpr_value() + |
| " || " + right_result.constexpr_value() + ")"); |
| } |
| |
| Block* true_block = assembler().NewBlock(); |
| Block* false_block = assembler().NewBlock(); |
| Block* done_block = assembler().NewBlock(); |
| |
| left_result = GenerateImplicitConvert(TypeOracle::GetBoolType(), left_result); |
| GenerateBranch(left_result, true_block, false_block); |
| |
| assembler().Bind(true_block); |
| VisitResult true_result = GenerateBoolConstant(true); |
| assembler().Goto(done_block); |
| |
| assembler().Bind(false_block); |
| VisitResult false_result; |
| { |
| StackScope false_block_scope(this); |
| false_result = false_block_scope.Yield( |
| GenerateImplicitConvert(TypeOracle::GetBoolType(), Visit(expr->right))); |
| } |
| assembler().Goto(done_block); |
| |
| assembler().Bind(done_block); |
| DCHECK_EQ(true_result, false_result); |
| return outer_scope.Yield(true_result); |
| } |
| |
| VisitResult ImplementationVisitor::Visit(LogicalAndExpression* expr) { |
| StackScope outer_scope(this); |
| VisitResult left_result = Visit(expr->left); |
| |
| if (left_result.type()->IsConstexprBool()) { |
| VisitResult right_result = Visit(expr->right); |
| if (!right_result.type()->IsConstexprBool()) { |
| ReportError( |
| "expected type constexpr bool on right-hand side of operator " |
| "&&"); |
| } |
| return VisitResult(TypeOracle::GetConstexprBoolType(), |
| std::string("(") + left_result.constexpr_value() + |
| " && " + right_result.constexpr_value() + ")"); |
| } |
| |
| Block* true_block = assembler().NewBlock(); |
| Block* false_block = assembler().NewBlock(); |
| Block* done_block = assembler().NewBlock(); |
| |
| left_result = GenerateImplicitConvert(TypeOracle::GetBoolType(), left_result); |
| GenerateBranch(left_result, true_block, false_block); |
| |
| assembler().Bind(true_block); |
| VisitResult true_result; |
| { |
| StackScope true_block_scope(this); |
| VisitResult right_result = Visit(expr->right); |
| if (TryGetSourceForBitfieldExpression(expr->left) != nullptr && |
| TryGetSourceForBitfieldExpression(expr->right) != nullptr && |
| TryGetSourceForBitfieldExpression(expr->left)->value == |
| TryGetSourceForBitfieldExpression(expr->right)->value) { |
| Lint( |
| "Please use & rather than && when checking multiple bitfield " |
| "values, to avoid complexity in generated code."); |
| } |
| true_result = true_block_scope.Yield( |
| GenerateImplicitConvert(TypeOracle::GetBoolType(), right_result)); |
| } |
| assembler().Goto(done_block); |
| |
| assembler().Bind(false_block); |
| VisitResult false_result = GenerateBoolConstant(false); |
| assembler().Goto(done_block); |
| |
| assembler().Bind(done_block); |
| DCHECK_EQ(true_result, false_result); |
| return outer_scope.Yield(true_result); |
| } |
| |
| VisitResult ImplementationVisitor::Visit(IncrementDecrementExpression* expr) { |
| StackScope scope(this); |
| LocationReference location_ref = GetLocationReference(expr->location); |
| VisitResult current_value = GenerateFetchFromLocation(location_ref); |
| VisitResult one = {TypeOracle::GetConstInt31Type(), "1"}; |
| Arguments args; |
| args.parameters = {current_value, one}; |
| VisitResult assignment_value = GenerateCall( |
| expr->op == IncrementDecrementOperator::kIncrement ? "+" : "-", args); |
| GenerateAssignToLocation(location_ref, assignment_value); |
| return scope.Yield(expr->postfix ? current_value : assignment_value); |
| } |
| |
| VisitResult ImplementationVisitor::Visit(AssignmentExpression* expr) { |
| StackScope scope(this); |
| LocationReference location_ref = GetLocationReference(expr->location); |
| VisitResult assignment_value; |
| if (expr->op) { |
| VisitResult location_value = GenerateFetchFromLocation(location_ref); |
| assignment_value = Visit(expr->value); |
| Arguments args; |
| args.parameters = {location_value, assignment_value}; |
| assignment_value = GenerateCall(*expr->op, args); |
| GenerateAssignToLocation(location_ref, assignment_value); |
| } else { |
| assignment_value = Visit(expr->value); |
| GenerateAssignToLocation(location_ref, assignment_value); |
| } |
| return scope.Yield(assignment_value); |
| } |
| |
| VisitResult ImplementationVisitor::Visit(NumberLiteralExpression* expr) { |
| const Type* result_type = TypeOracle::GetConstFloat64Type(); |
| if (expr->number >= std::numeric_limits<int32_t>::min() && |
| expr->number <= std::numeric_limits<int32_t>::max()) { |
| int32_t i = static_cast<int32_t>(expr->number); |
| if (i == expr->number) { |
| if ((i >> 30) == (i >> 31)) { |
| result_type = TypeOracle::GetConstInt31Type(); |
| } else { |
| result_type = TypeOracle::GetConstInt32Type(); |
| } |
| } |
| } |
| std::stringstream str; |
| str << std::setprecision(std::numeric_limits<double>::digits10 + 1) |
| << expr->number; |
| return VisitResult{result_type, str.str()}; |
| } |
| |
| VisitResult ImplementationVisitor::Visit(AssumeTypeImpossibleExpression* expr) { |
| VisitResult result = Visit(expr->expression); |
| const Type* result_type = SubtractType( |
| result.type(), TypeVisitor::ComputeType(expr->excluded_type)); |
| if (result_type->IsNever()) { |
| ReportError("unreachable code"); |
| } |
| CHECK_EQ(LowerType(result_type), TypeVector{result_type}); |
| assembler().Emit(UnsafeCastInstruction{result_type}); |
| result.SetType(result_type); |
| return result; |
| } |
| |
| VisitResult ImplementationVisitor::Visit(StringLiteralExpression* expr) { |
| return VisitResult{ |
| TypeOracle::GetConstStringType(), |
| "\"" + expr->literal.substr(1, expr->literal.size() - 2) + "\""}; |
| } |
| |
| VisitResult ImplementationVisitor::GetBuiltinCode(Builtin* builtin) { |
| if (builtin->IsExternal() || builtin->kind() != Builtin::kStub) { |
| ReportError( |
| "creating function pointers is only allowed for internal builtins with " |
| "stub linkage"); |
| } |
| const Type* type = TypeOracle::GetBuiltinPointerType( |
| builtin->signature().parameter_types.types, |
| builtin->signature().return_type); |
| assembler().Emit( |
| PushBuiltinPointerInstruction{builtin->ExternalName(), type}); |
| return VisitResult(type, assembler().TopRange(1)); |
| } |
| |
| VisitResult ImplementationVisitor::Visit(LocationExpression* expr) { |
| StackScope scope(this); |
| return scope.Yield(GenerateFetchFromLocation(GetLocationReference(expr))); |
| } |
| |
| VisitResult ImplementationVisitor::Visit(FieldAccessExpression* expr) { |
| StackScope scope(this); |
| LocationReference location = GetLocationReference(expr); |
| if (location.IsBitFieldAccess()) { |
| if (auto* identifier = IdentifierExpression::DynamicCast(expr->object)) { |
| bitfield_expressions_[expr] = identifier->name; |
| } |
| } |
| return scope.Yield(GenerateFetchFromLocation(location)); |
| } |
| |
| const Type* ImplementationVisitor::Visit(GotoStatement* stmt) { |
| Binding<LocalLabel>* label = LookupLabel(stmt->label->value); |
| size_t parameter_count = label->parameter_types.size(); |
| if (stmt->arguments.size() != parameter_count) { |
| ReportError("goto to label has incorrect number of parameters (expected ", |
| parameter_count, " found ", stmt->arguments.size(), ")"); |
| } |
| |
| if (GlobalContext::collect_language_server_data()) { |
| LanguageServerData::AddDefinition(stmt->label->pos, |
| label->declaration_position()); |
| } |
| |
| size_t i = 0; |
| StackRange arguments = assembler().TopRange(0); |
| for (Expression* e : stmt->arguments) { |
| StackScope scope(this); |
| VisitResult result = Visit(e); |
| const Type* parameter_type = label->parameter_types[i++]; |
| result = GenerateImplicitConvert(parameter_type, result); |
| arguments.Extend(scope.Yield(result).stack_range()); |
| } |
| |
| assembler().Goto(label->block, arguments.Size()); |
| return TypeOracle::GetNeverType(); |
| } |
| |
| const Type* ImplementationVisitor::Visit(IfStatement* stmt) { |
| bool has_else = stmt->if_false.has_value(); |
| |
| if (stmt->is_constexpr) { |
| VisitResult expression_result = Visit(stmt->condition); |
| |
| if (!(expression_result.type() == TypeOracle::GetConstexprBoolType())) { |
| std::stringstream stream; |
| stream << "expression should return type constexpr bool " |
| << "but returns type " << *expression_result.type(); |
| ReportError(stream.str()); |
| } |
| |
| Block* true_block = assembler().NewBlock(); |
| Block* false_block = assembler().NewBlock(); |
| Block* done_block = assembler().NewBlock(); |
| |
| assembler().Emit(ConstexprBranchInstruction{ |
| expression_result.constexpr_value(), true_block, false_block}); |
| |
| assembler().Bind(true_block); |
| const Type* left_result = Visit(stmt->if_true); |
| if (left_result == TypeOracle::GetVoidType()) { |
| assembler().Goto(done_block); |
| } |
| |
| assembler().Bind(false_block); |
| const Type* right_result = TypeOracle::GetVoidType(); |
| if (has_else) { |
| right_result = Visit(*stmt->if_false); |
| } |
| if (right_result == TypeOracle::GetVoidType()) { |
| assembler().Goto(done_block); |
| } |
| |
| if (left_result->IsNever() != right_result->IsNever()) { |
| std::stringstream stream; |
| stream << "either both or neither branches in a constexpr if statement " |
| "must reach their end at" |
| << PositionAsString(stmt->pos); |
| ReportError(stream.str()); |
| } |
| |
| if (left_result != TypeOracle::GetNeverType()) { |
| assembler().Bind(done_block); |
| } |
| return left_result; |
| } else { |
| Block* true_block = assembler().NewBlock(assembler().CurrentStack(), |
| IsDeferred(stmt->if_true)); |
| Block* false_block = |
| assembler().NewBlock(assembler().CurrentStack(), |
| stmt->if_false && IsDeferred(*stmt->if_false)); |
| GenerateExpressionBranch(stmt->condition, true_block, false_block); |
| |
| Block* done_block; |
| bool live = false; |
| if (has_else) { |
| done_block = assembler().NewBlock(); |
| } else { |
| done_block = false_block; |
| live = true; |
| } |
| |
| assembler().Bind(true_block); |
| { |
| const Type* result = Visit(stmt->if_true); |
| if (result == TypeOracle::GetVoidType()) { |
| live = true; |
| assembler().Goto(done_block); |
| } |
| } |
| |
| if (has_else) { |
| assembler().Bind(false_block); |
| const Type* result = Visit(*stmt->if_false); |
| if (result == TypeOracle::GetVoidType()) { |
| live = true; |
| assembler().Goto(done_block); |
| } |
| } |
| |
| if (live) { |
| assembler().Bind(done_block); |
| } |
| return live ? TypeOracle::GetVoidType() : TypeOracle::GetNeverType(); |
| } |
| } |
| |
| const Type* ImplementationVisitor::Visit(WhileStatement* stmt) { |
| Block* body_block = assembler().NewBlock(assembler().CurrentStack()); |
| Block* exit_block = assembler().NewBlock(assembler().CurrentStack()); |
| |
| Block* header_block = assembler().NewBlock(); |
| assembler().Goto(header_block); |
| |
| assembler().Bind(header_block); |
| GenerateExpressionBranch(stmt->condition, body_block, exit_block); |
| |
| assembler().Bind(body_block); |
| { |
| BreakContinueActivator activator{exit_block, header_block}; |
| const Type* body_result = Visit(stmt->body); |
| if (body_result != TypeOracle::GetNeverType()) { |
| assembler().Goto(header_block); |
| } |
| } |
| |
| assembler().Bind(exit_block); |
| return TypeOracle::GetVoidType(); |
| } |
| |
| const Type* ImplementationVisitor::Visit(BlockStatement* block) { |
| BlockBindings<LocalValue> block_bindings(&ValueBindingsManager::Get()); |
| const Type* type = TypeOracle::GetVoidType(); |
| for (Statement* s : block->statements) { |
| CurrentSourcePosition::Scope source_position(s->pos); |
| if (type->IsNever()) { |
| ReportError("statement after non-returning statement"); |
| } |
| if (auto* var_declaration = VarDeclarationStatement::DynamicCast(s)) { |
| type = Visit(var_declaration, &block_bindings); |
| } else { |
| type = Visit(s); |
| } |
| } |
| return type; |
| } |
| |
| const Type* ImplementationVisitor::Visit(DebugStatement* stmt) { |
| #if defined(DEBUG) |
| assembler().Emit(PrintConstantStringInstruction{"halting because of '" + |
| stmt->reason + "' at " + |
| PositionAsString(stmt->pos)}); |
| #endif |
| assembler().Emit(AbortInstruction{stmt->never_continues |
| ? AbortInstruction::Kind::kUnreachable |
| : AbortInstruction::Kind::kDebugBreak}); |
| if (stmt->never_continues) { |
| return TypeOracle::GetNeverType(); |
| } else { |
| return TypeOracle::GetVoidType(); |
| } |
| } |
| |
| namespace { |
| |
| std::string FormatAssertSource(const std::string& str) { |
| // Replace all whitespace characters with a space character. |
| std::string str_no_newlines = str; |
| std::replace_if( |
| str_no_newlines.begin(), str_no_newlines.end(), |
| [](unsigned char c) { return isspace(c); }, ' '); |
| |
| // str might include indentation, squash multiple space characters into one. |
| std::string result; |
| std::unique_copy(str_no_newlines.begin(), str_no_newlines.end(), |
| std::back_inserter(result), |
| [](char a, char b) { return a == ' ' && b == ' '; }); |
| return result; |
| } |
| |
| } // namespace |
| |
| const Type* ImplementationVisitor::Visit(AssertStatement* stmt) { |
| if (stmt->kind == AssertStatement::AssertKind::kStaticAssert) { |
| std::string message = |
| "static_assert(" + stmt->source + ") at " + ToString(stmt->pos); |
| GenerateCall(QualifiedName({"", TORQUE_INTERNAL_NAMESPACE_STRING}, |
| STATIC_ASSERT_MACRO_STRING), |
| Arguments{{Visit(stmt->expression), |
| VisitResult(TypeOracle::GetConstexprStringType(), |
| StringLiteralQuote(message))}, |
| {}}); |
| return TypeOracle::GetVoidType(); |
| } |
| bool do_check = stmt->kind != AssertStatement::AssertKind::kAssert || |
| GlobalContext::force_assert_statements(); |
| #if defined(DEBUG) |
| do_check = true; |
| #endif |
| Block* resume_block; |
| |
| if (!do_check) { |
| Block* unreachable_block = assembler().NewBlock(assembler().CurrentStack()); |
| resume_block = assembler().NewBlock(assembler().CurrentStack()); |
| assembler().Goto(resume_block); |
| assembler().Bind(unreachable_block); |
| } |
| |
| // CSA_ASSERT & co. are not used here on purpose for two reasons. First, |
| // Torque allows and handles two types of expressions in the if protocol |
| // automagically, ones that return TNode<BoolT> and those that use the |
| // BranchIf(..., Label* true, Label* false) idiom. Because the machinery to |
| // handle this is embedded in the expression handling and to it's not |
| // possible to make the decision to use CSA_ASSERT or CSA_ASSERT_BRANCH |
| // isn't trivial up-front. Secondly, on failure, the assert text should be |
| // the corresponding Torque code, not the -gen.cc code, which would be the |
| // case when using CSA_ASSERT_XXX. |
| Block* true_block = assembler().NewBlock(assembler().CurrentStack()); |
| Block* false_block = assembler().NewBlock(assembler().CurrentStack(), true); |
| GenerateExpressionBranch(stmt->expression, true_block, false_block); |
| |
| assembler().Bind(false_block); |
| |
| assembler().Emit(AbortInstruction{ |
| AbortInstruction::Kind::kAssertionFailure, |
| "Torque assert '" + FormatAssertSource(stmt->source) + "' failed"}); |
| |
| assembler().Bind(true_block); |
| |
| if (!do_check) { |
| assembler().Bind(resume_block); |
| } |
| |
| return TypeOracle::GetVoidType(); |
| } |
| |
| const Type* ImplementationVisitor::Visit(ExpressionStatement* stmt) { |
| const Type* type = Visit(stmt->expression).type(); |
| return type->IsNever() ? type : TypeOracle::GetVoidType(); |
| } |
| |
| const Type* ImplementationVisitor::Visit(ReturnStatement* stmt) { |
| Callable* current_callable = CurrentCallable::Get(); |
| if (current_callable->signature().return_type->IsNever()) { |
| std::stringstream s; |
| s << "cannot return from a function with return type never"; |
| ReportError(s.str()); |
| } |
| LocalLabel* end = |
| current_callable->IsMacro() ? LookupLabel(kMacroEndLabelName) : nullptr; |
| if (current_callable->HasReturnValue()) { |
| if (!stmt->value) { |
| std::stringstream s; |
| s << "return expression needs to be specified for a return type of " |
| << *current_callable->signature().return_type; |
| ReportError(s.str()); |
| } |
| VisitResult expression_result = Visit(*stmt->value); |
| VisitResult return_result = GenerateImplicitConvert( |
| current_callable->signature().return_type, expression_result); |
| if (current_callable->IsMacro()) { |
| if (return_result.IsOnStack()) { |
| StackRange return_value_range = |
| GenerateLabelGoto(end, return_result.stack_range()); |
| SetReturnValue(VisitResult(return_result.type(), return_value_range)); |
| } else { |
| GenerateLabelGoto(end); |
| SetReturnValue(return_result); |
| } |
| } else if (current_callable->IsBuiltin()) { |
| assembler().Emit(ReturnInstruction{}); |
| } else { |
| UNREACHABLE(); |
| } |
| } else { |
| if (stmt->value) { |
| std::stringstream s; |
| s << "return expression can't be specified for a void or never return " |
| "type"; |
| ReportError(s.str()); |
| } |
| GenerateLabelGoto(end); |
| } |
| current_callable->IncrementReturns(); |
| return TypeOracle::GetNeverType(); |
| } |
| |
| VisitResult ImplementationVisitor::Visit(TryLabelExpression* expr) { |
| size_t parameter_count = expr->label_block->parameters.names.size(); |
| std::vector<VisitResult> parameters; |
| |
| Block* label_block = nullptr; |
| Block* done_block = assembler().NewBlock(); |
| VisitResult try_result; |
| |
| { |
| CurrentSourcePosition::Scope source_position(expr->label_block->pos); |
| if (expr->label_block->parameters.has_varargs) { |
| ReportError("cannot use ... for label parameters"); |
| } |
| Stack<const Type*> label_input_stack = assembler().CurrentStack(); |
| TypeVector parameter_types; |
| for (size_t i = 0; i < parameter_count; ++i) { |
| const Type* type = |
| TypeVisitor::ComputeType(expr->label_block->parameters.types[i]); |
| parameter_types.push_back(type); |
| if (type->IsConstexpr()) { |
| ReportError("no constexpr type allowed for label arguments"); |
| } |
| StackRange range = label_input_stack.PushMany(LowerType(type)); |
| parameters.push_back(VisitResult(type, range)); |
| } |
| label_block = assembler().NewBlock(label_input_stack, |
| IsDeferred(expr->label_block->body)); |
| |
| Binding<LocalLabel> label_binding{&LabelBindingsManager::Get(), |
| expr->label_block->label, |
| LocalLabel{label_block, parameter_types}}; |
| |
| // Visit try |
| StackScope stack_scope(this); |
| try_result = Visit(expr->try_expression); |
| if (try_result.type() != TypeOracle::GetNeverType()) { |
| try_result = stack_scope.Yield(try_result); |
| assembler().Goto(done_block); |
| } |
| } |
| |
| // Visit and output the code for the label block. If the label block falls |
| // through, then the try must not return a value. Also, if the try doesn't |
| // fall through, but the label does, then overall the try-label block |
| // returns type void. |
| assembler().Bind(label_block); |
| const Type* label_result; |
| { |
| BlockBindings<LocalValue> parameter_bindings(&ValueBindingsManager::Get()); |
| for (size_t i = 0; i < parameter_count; ++i) { |
| Identifier* name = expr->label_block->parameters.names[i]; |
| parameter_bindings.Add(name, |
| LocalValue{LocationReference::Temporary( |
| parameters[i], "parameter " + name->value)}); |
| } |
| |
| label_result = Visit(expr->label_block->body); |
| } |
| if (!try_result.type()->IsVoidOrNever() && label_result->IsVoid()) { |
| ReportError( |
| "otherwise clauses cannot fall through in a non-void expression"); |
| } |
| if (label_result != TypeOracle::GetNeverType()) { |
| assembler().Goto(done_block); |
| } |
| if (label_result->IsVoid() && try_result.type()->IsNever()) { |
| try_result = |
| VisitResult(TypeOracle::GetVoidType(), try_result.stack_range()); |
| } |
| |
| if (!try_result.type()->IsNever()) { |
| assembler().Bind(done_block); |
| } |
| return try_result; |
| } |
| |
| VisitResult ImplementationVisitor::Visit(StatementExpression* expr) { |
| return VisitResult{Visit(expr->statement), assembler().TopRange(0)}; |
| } |
| |
| InitializerResults ImplementationVisitor::VisitInitializerResults( |
| const ClassType* class_type, |
| const std::vector<NameAndExpression>& initializers) { |
| InitializerResults result; |
| for (const NameAndExpression& initializer : initializers) { |
| result.names.push_back(initializer.name); |
| Expression* e = initializer.expression; |
| const Field& field = class_type->LookupField(initializer.name->value); |
| bool has_index = field.index.has_value(); |
| if (SpreadExpression* s = SpreadExpression::DynamicCast(e)) { |
| if (!has_index) { |
| ReportError( |
| "spread expressions can only be used to initialize indexed class " |
| "fields ('", |
| initializer.name->value, "' is not)"); |
| } |
| e = s->spreadee; |
| } else if (has_index) { |
| ReportError("the indexed class field '", initializer.name->value, |
| "' must be initialized with a spread operator"); |
| } |
| result.field_value_map[field.name_and_type.name] = Visit(e); |
| } |
| return result; |
| } |
| |
| LocationReference ImplementationVisitor::GenerateFieldReference( |
| VisitResult object, const Field& field, const ClassType* class_type) { |
| if (field.index.has_value()) { |
| return LocationReference::HeapSlice( |
| GenerateCall(class_type->GetSliceMacroName(field), {{object}, {}})); |
| } |
| DCHECK(field.offset.has_value()); |
| StackRange result_range = assembler().TopRange(0); |
| result_range.Extend(GenerateCopy(object).stack_range()); |
| VisitResult offset = |
| VisitResult(TypeOracle::GetConstInt31Type(), ToString(*field.offset)); |
| offset = GenerateImplicitConvert(TypeOracle::GetIntPtrType(), offset); |
| result_range.Extend(offset.stack_range()); |
| const Type* type = TypeOracle::GetReferenceType(field.name_and_type.type, |
| field.const_qualified); |
| return LocationReference::HeapReference(VisitResult(type, result_range)); |
| } |
| |
| // This is used to generate field references during initialization, where we can |
| // re-use the offsets used for computing the allocation size. |
| LocationReference ImplementationVisitor::GenerateFieldReferenceForInit( |
| VisitResult object, const Field& field, |
| const LayoutForInitialization& layout) { |
| StackRange result_range = assembler().TopRange(0); |
| result_range.Extend(GenerateCopy(object).stack_range()); |
| VisitResult offset = GenerateImplicitConvert( |
| TypeOracle::GetIntPtrType(), layout.offsets.at(field.name_and_type.name)); |
| result_range.Extend(offset.stack_range()); |
| if (field.index) { |
| VisitResult length = |
| GenerateCopy(layout.array_lengths.at(field.name_and_type.name)); |
| result_range.Extend(length.stack_range()); |
| const Type* slice_type = TypeOracle::GetSliceType(field.name_and_type.type); |
| return LocationReference::HeapSlice(VisitResult(slice_type, result_range)); |
| } else { |
| // Const fields are writable during initialization. |
| VisitResult heap_reference( |
| TypeOracle::GetMutableReferenceType(field.name_and_type.type), |
| result_range); |
| return LocationReference::HeapReference(heap_reference); |
| } |
| } |
| |
| void ImplementationVisitor::InitializeClass( |
| const ClassType* class_type, VisitResult allocate_result, |
| const InitializerResults& initializer_results, |
| const LayoutForInitialization& layout) { |
| if (const ClassType* super = class_type->GetSuperClass()) { |
| InitializeClass(super, allocate_result, initializer_results, layout); |
| } |
| |
| for (Field f : class_type->fields()) { |
| VisitResult initializer_value = |
| initializer_results.field_value_map.at(f.name_and_type.name); |
| LocationReference field = |
| GenerateFieldReferenceForInit(allocate_result, f, layout); |
| if (f.index) { |
| DCHECK(field.IsHeapSlice()); |
| VisitResult slice = field.GetVisitResult(); |
| GenerateCall(QualifiedName({TORQUE_INTERNAL_NAMESPACE_STRING}, |
| "InitializeFieldsFromIterator"), |
| {{slice, initializer_value}, {}}); |
| } else { |
| GenerateAssignToLocation(field, initializer_value); |
| } |
| } |
| } |
| |
| VisitResult ImplementationVisitor::GenerateArrayLength( |
| Expression* array_length, Namespace* nspace, |
| const std::map<std::string, LocalValue>& bindings) { |
| StackScope stack_scope(this); |
| CurrentSourcePosition::Scope pos_scope(array_length->pos); |
| // Switch to the namespace where the class was declared. |
| CurrentScope::Scope current_scope_scope(nspace); |
| // Reset local bindings and install local binding for the preceding fields. |
| BindingsManagersScope bindings_managers_scope; |
| BlockBindings<LocalValue> field_bindings(&ValueBindingsManager::Get()); |
| for (auto& p : bindings) { |
| field_bindings.Add(p.first, LocalValue{p.second}, true); |
| } |
| VisitResult length = Visit(array_length); |
| VisitResult converted_length = |
| GenerateCall("Convert", Arguments{{length}, {}}, |
| {TypeOracle::GetIntPtrType(), length.type()}, false); |
| return stack_scope.Yield(converted_length); |
| } |
| |
| VisitResult ImplementationVisitor::GenerateArrayLength(VisitResult object, |
| const Field& field) { |
| DCHECK(field.index); |
| |
| StackScope stack_scope(this); |
| const ClassType* class_type = *object.type()->ClassSupertype(); |
| std::map<std::string, LocalValue> bindings; |
| bool before_current = true; |
| for (Field f : class_type->ComputeAllFields()) { |
| if (field.name_and_type.name == f.name_and_type.name) { |
| before_current = false; |
| } |
| bindings.insert( |
| {f.name_and_type.name, |
| f.const_qualified |
| ? (before_current |
| ? LocalValue{GenerateFieldReference(object, f, class_type)} |
| : LocalValue("Array lengths may only refer to fields " |
| "defined earlier")) |
| : LocalValue( |
| "Non-const fields cannot be used for array lengths.")}); |
| } |
| return stack_scope.Yield( |
| GenerateArrayLength(*field.index, class_type->nspace(), bindings)); |
| } |
| |
| VisitResult ImplementationVisitor::GenerateArrayLength( |
| const ClassType* class_type, const InitializerResults& initializer_results, |
| const Field& field) { |
| DCHECK(field.index); |
| |
| StackScope stack_scope(this); |
| std::map<std::string, LocalValue> bindings; |
| for (Field f : class_type->ComputeAllFields()) { |
| if (f.index) break; |
| const std::string& fieldname = f.name_and_type.name; |
| VisitResult value = initializer_results.field_value_map.at(fieldname); |
| bindings.insert( |
| {fieldname, |
| f.const_qualified |
| ? LocalValue{LocationReference::Temporary( |
| value, "initial field " + fieldname)} |
| : LocalValue( |
| "Non-const fields cannot be used for array lengths.")}); |
| } |
| return stack_scope.Yield( |
| GenerateArrayLength(*field.index, class_type->nspace(), bindings)); |
| } |
| |
| LayoutForInitialization ImplementationVisitor::GenerateLayoutForInitialization( |
| const ClassType* class_type, |
| const InitializerResults& initializer_results) { |
| LayoutForInitialization layout; |
| VisitResult offset; |
| for (Field f : class_type->ComputeAllFields()) { |
| if (f.offset.has_value()) { |
| offset = |
| VisitResult(TypeOracle::GetConstInt31Type(), ToString(*f.offset)); |
| } |
| layout.offsets[f.name_and_type.name] = offset; |
| if (f.index) { |
| size_t element_size; |
| std::string element_size_string; |
| std::tie(element_size, element_size_string) = |
| *SizeOf(f.name_and_type.type); |
| VisitResult array_element_size = |
| VisitResult(TypeOracle::GetConstInt31Type(), element_size_string); |
| VisitResult array_length = |
| GenerateArrayLength(class_type, initializer_results, f); |
| layout.array_lengths[f.name_and_type.name] = array_length; |
| Arguments arguments; |
| arguments.parameters = {offset, array_length, array_element_size}; |
| offset = GenerateCall(QualifiedName({TORQUE_INTERNAL_NAMESPACE_STRING}, |
| "AddIndexedFieldSizeToObjectSize"), |
| arguments); |
| } else { |
| DCHECK(f.offset.has_value()); |
| } |
| } |
| if (class_type->size().SingleValue()) { |
| layout.size = VisitResult(TypeOracle::GetConstInt31Type(), |
| ToString(*class_type->size().SingleValue())); |
| } else { |
| layout.size = offset; |
| } |
| if ((size_t{1} << class_type->size().AlignmentLog2()) < |
| TargetArchitecture::TaggedSize()) { |
| Arguments arguments; |
| arguments.parameters = {layout.size}; |
| layout.size = GenerateCall( |
| QualifiedName({TORQUE_INTERNAL_NAMESPACE_STRING}, "AlignTagged"), |
| arguments); |
| } |
| return layout; |
| } |
| |
| VisitResult ImplementationVisitor::Visit(NewExpression* expr) { |
| StackScope stack_scope(this); |
| const Type* type = TypeVisitor::ComputeType(expr->type); |
| const ClassType* class_type = ClassType::DynamicCast(type); |
| if (class_type == nullptr) { |
| ReportError("type for new expression must be a class, \"", *type, |
| "\" is not"); |
| } |
| |
| if (!class_type->AllowInstantiation()) { |
| // Classes that are only used for testing should never be instantiated. |
| ReportError(*class_type, |
| " cannot be allocated with new (it's used for testing)"); |
| } |
| |
| InitializerResults initializer_results = |
| VisitInitializerResults(class_type, expr->initializers); |
| |
| const Field& map_field = class_type->LookupField("map"); |
| if (*map_field.offset != 0) { |
| ReportError("class initializers must have a map as first parameter"); |
| } |
| const std::map<std::string, VisitResult>& initializer_fields = |
| initializer_results.field_value_map; |
| auto it_object_map = initializer_fields.find(map_field.name_and_type.name); |
| VisitResult object_map; |
| if (class_type->IsExtern()) { |
| if (it_object_map == initializer_fields.end()) { |
| ReportError("Constructor for ", class_type->name(), |
| " needs Map argument!"); |
| } |
| object_map = it_object_map->second; |
| } else { |
| if (it_object_map != initializer_fields.end()) { |
| ReportError( |
| "Constructor for ", class_type->name(), |
| " must not specify Map argument; it is automatically inserted."); |
| } |
| Arguments get_struct_map_arguments; |
| get_struct_map_arguments.parameters.push_back( |
| VisitResult(TypeOracle::GetConstexprInstanceTypeType(), |
| CapifyStringWithUnderscores(class_type->name()) + "_TYPE")); |
| object_map = GenerateCall( |
| QualifiedName({TORQUE_INTERNAL_NAMESPACE_STRING}, "GetInstanceTypeMap"), |
| get_struct_map_arguments, {}, false); |
| CurrentSourcePosition::Scope current_pos(expr->pos); |
| initializer_results.names.insert(initializer_results.names.begin(), |
| MakeNode<Identifier>("map")); |
| initializer_results.field_value_map[map_field.name_and_type.name] = |
| object_map; |
| } |
| |
| CheckInitializersWellformed(class_type->name(), |
| class_type->ComputeAllFields(), |
| expr->initializers, !class_type->IsExtern()); |
| |
| LayoutForInitialization layout = |
| GenerateLayoutForInitialization(class_type, initializer_results); |
| |
| Arguments allocate_arguments; |
| allocate_arguments.parameters.push_back(layout.size); |
| allocate_arguments.parameters.push_back(object_map); |
| allocate_arguments.parameters.push_back( |
| GenerateBoolConstant(expr->pretenured)); |
| VisitResult allocate_result = GenerateCall( |
| QualifiedName({TORQUE_INTERNAL_NAMESPACE_STRING}, "AllocateFromNew"), |
| allocate_arguments, {class_type}, false); |
| DCHECK(allocate_result.IsOnStack()); |
| |
| InitializeClass(class_type, allocate_result, initializer_results, layout); |
| |
| return stack_scope.Yield(GenerateCall( |
| "%RawDownCast", Arguments{{allocate_result}, {}}, {class_type})); |
| } |
| |
| const Type* ImplementationVisitor::Visit(BreakStatement* stmt) { |
| base::Optional<Binding<LocalLabel>*> break_label = |
| TryLookupLabel(kBreakLabelName); |
| if (!break_label) { |
| ReportError("break used outside of loop"); |
| } |
| assembler().Goto((*break_label)->block); |
| return TypeOracle::GetNeverType(); |
| } |
| |
| const Type* ImplementationVisitor::Visit(ContinueStatement* stmt) { |
| base::Optional<Binding<LocalLabel>*> continue_label = |
| TryLookupLabel(kContinueLabelName); |
| if (!continue_label) { |
| ReportError("continue used outside of loop"); |
| } |
| assembler().Goto((*continue_label)->block); |
| return TypeOracle::GetNeverType(); |
| } |
| |
| const Type* ImplementationVisitor::Visit(ForLoopStatement* stmt) { |
| BlockBindings<LocalValue> loop_bindings(&ValueBindingsManager::Get()); |
| |
| if (stmt->var_declaration) Visit(*stmt->var_declaration, &loop_bindings); |
| |
| Block* body_block = assembler().NewBlock(assembler().CurrentStack()); |
| Block* exit_block = assembler().NewBlock(assembler().CurrentStack()); |
| |
| Block* header_block = assembler().NewBlock(); |
| assembler().Goto(header_block); |
| assembler().Bind(header_block); |
| |
| // The continue label is where "continue" statements jump to. If no action |
| // expression is provided, we jump directly to the header. |
| Block* continue_block = header_block; |
| |
| // The action label is only needed when an action expression was provided. |
| Block* action_block = nullptr; |
| if (stmt->action) { |
| action_block = assembler().NewBlock(); |
| |
| // The action expression needs to be executed on a continue. |
| continue_block = action_block; |
| } |
| |
| if (stmt->test) { |
| GenerateExpressionBranch(*stmt->test, body_block, exit_block); |
| } else { |
| assembler().Goto(body_block); |
| } |
| |
| assembler().Bind(body_block); |
| { |
| BreakContinueActivator activator(exit_block, continue_block); |
| const Type* body_result = Visit(stmt->body); |
| if (body_result != TypeOracle::GetNeverType()) { |
| assembler().Goto(continue_block); |
| } |
| } |
| |
| if (stmt->action) { |
| assembler().Bind(action_block); |
| const Type* action_result = Visit(*stmt->action); |
| if (action_result != TypeOracle::GetNeverType()) { |
| assembler().Goto(header_block); |
| } |
| } |
| |
| assembler().Bind(exit_block); |
| return TypeOracle::GetVoidType(); |
| } |
| |
| VisitResult ImplementationVisitor::Visit(SpreadExpression* expr) { |
| ReportError( |
| "spread operators are only currently supported in indexed class field " |
| "initialization expressions"); |
| } |
| |
| void ImplementationVisitor::GenerateImplementation(const std::string& dir) { |
| for (SourceId file : SourceFileMap::AllSources()) { |
| std::string base_filename = |
| dir + "/" + SourceFileMap::PathFromV8RootWithoutExtension(file); |
| GlobalContext::PerFileStreams& streams = |
| GlobalContext::GeneratedPerFile(file); |
| |
| WriteFile(base_filename + "-tq-csa.cc", streams.csa_ccfile.str()); |
| WriteFile(base_filename + "-tq-csa.h", streams.csa_headerfile.str()); |
| WriteFile(base_filename + "-tq.inc", |
| streams.class_definition_headerfile.str()); |
| WriteFile(base_filename + "-tq-inl.inc", |
| streams.class_definition_inline_headerfile.str()); |
| WriteFile(base_filename + "-tq.cc", streams.class_definition_ccfile.str()); |
| } |
| |
| WriteFile(dir + "/runtime-macros.h", runtime_macros_h_.str()); |
| WriteFile(dir + "/runtime-macros.cc", runtime_macros_cc_.str()); |
| } |
| |
| void ImplementationVisitor::GenerateMacroFunctionDeclaration(std::ostream& o, |
| Macro* macro) { |
| GenerateFunctionDeclaration( |
| o, "", |
| output_type_ == OutputType::kCC ? macro->CCName() : macro->ExternalName(), |
| macro->signature(), macro->parameter_names()); |
| } |
| |
| std::vector<std::string> ImplementationVisitor::GenerateFunctionDeclaration( |
| std::ostream& o, const std::string& macro_prefix, const std::string& name, |
| const Signature& signature, const NameVector& parameter_names, |
| bool pass_code_assembler_state) { |
| std::vector<std::string> generated_parameter_names; |
| if (signature.return_type->IsVoidOrNever()) { |
| o << "void"; |
| } else { |
| o << (output_type_ == OutputType::kCC |
| ? signature.return_type->GetRuntimeType() |
| : signature.return_type->GetGeneratedTypeName()); |
| } |
| o << " " << macro_prefix << name << "("; |
| |
| bool first = true; |
| if (output_type_ == OutputType::kCC) { |
| first = false; |
| o << "Isolate* isolate"; |
| } else if (pass_code_assembler_state) { |
| first = false; |
| o << "compiler::CodeAssemblerState* state_"; |
| } |
| |
| DCHECK_GE(signature.types().size(), parameter_names.size()); |
| for (size_t i = 0; i < signature.types().size(); ++i) { |
| if (!first) o << ", "; |
| first = false; |
| const Type* parameter_type = signature.types()[i]; |
| const std::string& generated_type_name = |
| output_type_ == OutputType::kCC |
| ? parameter_type->GetRuntimeType() |
| : parameter_type->GetGeneratedTypeName(); |
| |
| generated_parameter_names.push_back(ExternalParameterName( |
| i < parameter_names.size() ? parameter_names[i]->value |
| : std::to_string(i))); |
| o << generated_type_name << " " << generated_parameter_names.back(); |
| } |
| |
| for (const LabelDeclaration& label_info : signature.labels) { |
| if (output_type_ == OutputType::kCC) { |
| ReportError("Macros that generate runtime code can't have label exits"); |
| } |
| if (!first) o << ", "; |
| first = false; |
| generated_parameter_names.push_back( |
| ExternalLabelName(label_info.name->value)); |
| o << "compiler::CodeAssemblerLabel* " << generated_parameter_names.back(); |
| size_t i = 0; |
| for (const Type* type : label_info.types) { |
| std::string generated_type_name; |
| if (type->StructSupertype()) { |
| generated_type_name = "\n#error no structs allowed in labels\n"; |
| } else { |
| generated_type_name = "compiler::TypedCodeAssemblerVariable<"; |
| generated_type_name += type->GetGeneratedTNodeTypeName(); |
| generated_type_name += ">*"; |
| } |
| o << ", "; |
| generated_parameter_names.push_back( |
| ExternalLabelParameterName(label_info.name->value, i)); |
| o << generated_type_name << " " << generated_parameter_names.back(); |
| ++i; |
| } |
| } |
| |
| o << ")"; |
| return generated_parameter_names; |
| } |
| |
| namespace { |
| |
| void FailCallableLookup( |
| const std::string& reason, const QualifiedName& name, |
| const TypeVector& parameter_types, |
| const std::vector<Binding<LocalLabel>*>& labels, |
| const std::vector<Signature>& candidates, |
| const std::vector<std::pair<GenericCallable*, std::string>> |
| inapplicable_generics) { |
| std::stringstream stream; |
| stream << "\n" << reason << ": \n " << name << "(" << parameter_types << ")"; |
| if (labels.size() != 0) { |
| stream << " labels "; |
| for (size_t i = 0; i < labels.size(); ++i) { |
| stream << labels[i]->name() << "(" << labels[i]->parameter_types << ")"; |
| } |
| } |
| stream << "\ncandidates are:"; |
| for (const Signature& signature : candidates) { |
| stream << "\n " << name; |
| PrintSignature(stream, signature, false); |
| } |
| if (inapplicable_generics.size() != 0) { |
| stream << "\nfailed to instantiate all of these generic declarations:"; |
| for (auto& failure : inapplicable_generics) { |
| GenericCallable* generic = failure.first; |
| const std::string& reason = failure.second; |
| stream << "\n " << generic->name() << " defined at " |
| << generic->Position() << ":\n " << reason << "\n"; |
| } |
| } |
| ReportError(stream.str()); |
| } |
| |
| Callable* GetOrCreateSpecialization( |
| const SpecializationKey<GenericCallable>& key) { |
| if (base::Optional<Callable*> specialization = |
| key.generic->GetSpecialization(key.specialized_types)) { |
| return *specialization; |
| } |
| return DeclarationVisitor::SpecializeImplicit(key); |
| } |
| |
| } // namespace |
| |
| base::Optional<Binding<LocalValue>*> ImplementationVisitor::TryLookupLocalValue( |
| const std::string& name) { |
| return ValueBindingsManager::Get().TryLookup(name); |
| } |
| |
| base::Optional<Binding<LocalLabel>*> ImplementationVisitor::TryLookupLabel( |
| const std::string& name) { |
| return LabelBindingsManager::Get().TryLookup(name); |
| } |
| |
| Binding<LocalLabel>* ImplementationVisitor::LookupLabel( |
| const std::string& name) { |
| base::Optional<Binding<LocalLabel>*> label = TryLookupLabel(name); |
| if (!label) ReportError("cannot find label ", name); |
| return *label; |
| } |
| |
| Block* ImplementationVisitor::LookupSimpleLabel(const std::string& name) { |
| LocalLabel* label = LookupLabel(name); |
| if (!label->parameter_types.empty()) { |
| ReportError("label ", name, |
| "was expected to have no parameters, but has parameters (", |
| label->parameter_types, ")"); |
| } |
| return label->block; |
| } |
| |
| // Try to lookup a callable with the provided argument types. Do not report |
| // an error if no matching callable was found, but return false instead. |
| // This is used to test the presence of overloaded field accessors. |
| bool ImplementationVisitor::TestLookupCallable( |
| const QualifiedName& name, const TypeVector& parameter_types) { |
| return LookupCallable(name, Declarations::TryLookup(name), parameter_types, |
| {}, {}, true) != nullptr; |
| } |
| |
| TypeArgumentInference ImplementationVisitor::InferSpecializationTypes( |
| GenericCallable* generic, const TypeVector& explicit_specialization_types, |
| const TypeVector& explicit_arguments) { |
| std::vector<base::Optional<const Type*>> all_arguments; |
| const ParameterList& parameters = generic->declaration()->parameters; |
| for (size_t i = 0; i < parameters.implicit_count; ++i) { |
| base::Optional<Binding<LocalValue>*> val = |
| TryLookupLocalValue(parameters.names[i]->value); |
| all_arguments.push_back( |
| val ? (*val)->GetLocationReference(*val).ReferencedType() |
| : base::nullopt); |
| } |
| for (const Type* explicit_argument : explicit_arguments) { |
| all_arguments.push_back(explicit_argument); |
| } |
| return generic->InferSpecializationTypes(explicit_specialization_types, |
| all_arguments); |
| } |
| |
| template <class Container> |
| Callable* ImplementationVisitor::LookupCallable( |
| const QualifiedName& name, const Container& declaration_container, |
| const TypeVector& parameter_types, |
| const std::vector<Binding<LocalLabel>*>& labels, |
| const TypeVector& specialization_types, bool silence_errors) { |
| Callable* result = nullptr; |
| |
| std::vector<Declarable*> overloads; |
| std::vector<Signature> overload_signatures; |
| std::vector<std::pair<GenericCallable*, std::string>> inapplicable_generics; |
| for (auto* declarable : declaration_container) { |
| if (GenericCallable* generic = GenericCallable::DynamicCast(declarable)) { |
| TypeArgumentInference inference = InferSpecializationTypes( |
| generic, specialization_types, parameter_types); |
| if (inference.HasFailed()) { |
| inapplicable_generics.push_back( |
| std::make_pair(generic, inference.GetFailureReason())); |
| continue; |
| } |
| overloads.push_back(generic); |
| overload_signatures.push_back( |
| DeclarationVisitor::MakeSpecializedSignature( |
| SpecializationKey<GenericCallable>{generic, |
| inference.GetResult()})); |
| } else if (Callable* callable = Callable::DynamicCast(declarable)) { |
| overloads.push_back(callable); |
| overload_signatures.push_back(callable->signature()); |
| } |
| } |
| // Indices of candidates in overloads/overload_signatures. |
| std::vector<size_t> candidates; |
| for (size_t i = 0; i < overloads.size(); ++i) { |
| const Signature& signature = overload_signatures[i]; |
| if (IsCompatibleSignature(signature, parameter_types, labels.size())) { |
| candidates.push_back(i); |
| } |
| } |
| |
| if (overloads.empty() && inapplicable_generics.empty()) { |
| if (silence_errors) return nullptr; |
| std::stringstream stream; |
| stream << "no matching declaration found for " << name; |
| ReportError(stream.str()); |
| } else if (candidates.empty()) { |
| if (silence_errors) return nullptr; |
| FailCallableLookup("cannot find suitable callable with name", name, |
| parameter_types, labels, overload_signatures, |
| inapplicable_generics); |
| } |
| |
| auto is_better_candidate = [&](size_t a, size_t b) { |
| return ParameterDifference(overload_signatures[a].GetExplicitTypes(), |
| parameter_types) |
| .StrictlyBetterThan(ParameterDifference( |
| overload_signatures[b].GetExplicitTypes(), parameter_types)); |
| }; |
| |
| size_t best = *std::min_element(candidates.begin(), candidates.end(), |
| is_better_candidate); |
| // This check is contained in libstdc++'s std::min_element. |
| DCHECK(!is_better_candidate(best, best)); |
| for (size_t candidate : candidates) { |
| if (candidate != best && !is_better_candidate(best, candidate)) { |
| std::vector<Signature> candidate_signatures; |
| for (size_t i : candidates) { |
| candidate_signatures.push_back(overload_signatures[i]); |
| } |
| FailCallableLookup("ambiguous callable ", name, parameter_types, labels, |
| candidate_signatures, inapplicable_generics); |
| } |
| } |
| |
| if (GenericCallable* generic = |
| GenericCallable::DynamicCast(overloads[best])) { |
| TypeArgumentInference inference = InferSpecializationTypes( |
| generic, specialization_types, parameter_types); |
| result = GetOrCreateSpecialization( |
| SpecializationKey<GenericCallable>{generic, inference.GetResult()}); |
| } else { |
| result = Callable::cast(overloads[best]); |
| } |
| |
| size_t caller_size = parameter_types.size(); |
| size_t callee_size = |
| result->signature().types().size() - result->signature().implicit_count; |
| if (caller_size != callee_size && |
| !result->signature().parameter_types.var_args) { |
| std::stringstream stream; |
| stream << "parameter count mismatch calling " << *result << " - expected " |
| << std::to_string(callee_size) << ", found " |
| << std::to_string(caller_size); |
| ReportError(stream.str()); |
| } |
| |
| return result; |
| } |
| |
| template <class Container> |
| Callable* ImplementationVisitor::LookupCallable( |
| const QualifiedName& name, const Container& declaration_container, |
| const Arguments& arguments, const TypeVector& specialization_types) { |
| return LookupCallable(name, declaration_container, |
| arguments.parameters.ComputeTypeVector(), |
| arguments.labels, specialization_types); |
| } |
| |
| Method* ImplementationVisitor::LookupMethod( |
| const std::string& name, const AggregateType* receiver_type, |
| const Arguments& arguments, const TypeVector& specialization_types) { |
| TypeVector types(arguments.parameters.ComputeTypeVector()); |
| types.insert(types.begin(), receiver_type); |
| return Method::cast(LookupCallable({{}, name}, receiver_type->Methods(name), |
| types, arguments.labels, |
| specialization_types)); |
| } |
| |
| const Type* ImplementationVisitor::GetCommonType(const Type* left, |
| const Type* right) { |
| const Type* common_type; |
| if (IsAssignableFrom(left, right)) { |
| common_type = left; |
| } else if (IsAssignableFrom(right, left)) { |
| common_type = right; |
| } else { |
| common_type = TypeOracle::GetUnionType(left, right); |
| } |
| common_type = common_type->NonConstexprVersion(); |
| return common_type; |
| } |
| |
| VisitResult ImplementationVisitor::GenerateCopy(const VisitResult& to_copy) { |
| if (to_copy.IsOnStack()) { |
| return VisitResult(to_copy.type(), |
| assembler().Peek(to_copy.stack_range(), to_copy.type())); |
| } |
| return to_copy; |
| } |
| |
| VisitResult ImplementationVisitor::Visit(StructExpression* expr) { |
| StackScope stack_scope(this); |
| |
| auto& initializers = expr->initializers; |
| std::vector<VisitResult> values; |
| std::vector<const Type*> term_argument_types; |
| values.reserve(initializers.size()); |
| term_argument_types.reserve(initializers.size()); |
| |
| // Compute values and types of all initializer arguments |
| for (const NameAndExpression& initializer : initializers) { |
| VisitResult value = Visit(initializer.expression); |
| values.push_back(value); |
| term_argument_types.push_back(value.type()); |
| } |
| |
| // Compute and check struct type from given struct name and argument types |
| const Type* type = TypeVisitor::ComputeTypeForStructExpression( |
| expr->type, term_argument_types); |
| if (const auto* struct_type = StructType::DynamicCast(type)) { |
| CheckInitializersWellformed(struct_type->name(), struct_type->fields(), |
| initializers); |
| |
| // Implicitly convert values and thereby build the struct on the stack |
| StackRange struct_range = assembler().TopRange(0); |
| auto& fields = struct_type->fields(); |
| for (size_t i = 0; i < values.size(); i++) { |
| values[i] = |
| GenerateImplicitConvert(fields[i].name_and_type.type, values[i]); |
| struct_range.Extend(values[i].stack_range()); |
| } |
| |
| return stack_scope.Yield(VisitResult(struct_type, struct_range)); |
| } else { |
| const auto* bitfield_struct_type = BitFieldStructType::cast(type); |
| CheckInitializersWellformed(bitfield_struct_type->name(), |
| bitfield_struct_type->fields(), initializers); |
| |
| // Create a zero and cast it to the desired bitfield struct type. |
| VisitResult result{TypeOracle::GetConstInt32Type(), "0"}; |
| result = GenerateImplicitConvert(TypeOracle::GetInt32Type(), result); |
| result = GenerateCall("Unsigned", Arguments{{result}, {}}, {}); |
| result = GenerateCall("%RawDownCast", Arguments{{result}, {}}, |
| {bitfield_struct_type}); |
| |
| // Set each field in the result. If these fields are constexpr, then all of |
| // this initialization will end up reduced to a single value during TurboFan |
| // optimization. |
| auto& fields = bitfield_struct_type->fields(); |
| for (size_t i = 0; i < values.size(); i++) { |
| values[i] = |
| GenerateImplicitConvert(fields[i].name_and_type.type, values[i]); |
| result = GenerateSetBitField(bitfield_struct_type, fields[i], result, |
| values[i], /*starts_as_zero=*/true); |
| } |
| |
| return stack_scope.Yield(result); |
| } |
| } |
| |
| VisitResult ImplementationVisitor::GenerateSetBitField( |
| const Type* bitfield_struct_type, const BitField& bitfield, |
| VisitResult bitfield_struct, VisitResult value, bool starts_as_zero) { |
| GenerateCopy(bitfield_struct); |
| GenerateCopy(value); |
| assembler().Emit( |
| StoreBitFieldInstruction{bitfield_struct_type, bitfield, starts_as_zero}); |
| return VisitResult(bitfield_struct_type, assembler().TopRange(1)); |
| } |
| |
| LocationReference ImplementationVisitor::GetLocationReference( |
| Expression* location) { |
| switch (location->kind) { |
| case AstNode::Kind::kIdentifierExpression: |
| return GetLocationReference(static_cast<IdentifierExpression*>(location)); |
| case AstNode::Kind::kFieldAccessExpression: |
| return GetLocationReference( |
| static_cast<FieldAccessExpression*>(location)); |
| case AstNode::Kind::kElementAccessExpression: |
| return GetLocationReference( |
| static_cast<ElementAccessExpression*>(location)); |
| case AstNode::Kind::kDereferenceExpression: |
| return GetLocationReference( |
| static_cast<DereferenceExpression*>(location)); |
| default: |
| return LocationReference::Temporary(Visit(location), "expression"); |
| } |
| } |
| |
| LocationReference ImplementationVisitor::GetLocationReference( |
| FieldAccessExpression* expr) { |
| return GenerateFieldAccess(GetLocationReference(expr->object), |
| expr->field->value, false, expr->field->pos); |
| } |
| |
| LocationReference ImplementationVisitor::GenerateFieldAccess( |
| LocationReference reference, const std::string& fieldname, |
| bool ignore_stuct_field_constness, base::Optional<SourcePosition> pos) { |
| if (reference.IsVariableAccess() && |
| reference.variable().type()->StructSupertype()) { |
| const StructType* type = *reference.variable().type()->StructSupertype(); |
| const Field& field = type->LookupField(fieldname); |
| if (GlobalContext::collect_language_server_data() && pos.has_value()) { |
| LanguageServerData::AddDefinition(*pos, field.pos); |
| } |
| if (field.const_qualified) { |
| VisitResult t_value = ProjectStructField(reference.variable(), fieldname); |
| return LocationReference::Temporary( |
| t_value, "for constant field '" + field.name_and_type.name + "'"); |
| } else { |
| return LocationReference::VariableAccess( |
| ProjectStructField(reference.variable(), fieldname)); |
| } |
| } |
| if (reference.IsTemporary() && |
| reference.temporary().type()->StructSupertype()) { |
| if (GlobalContext::collect_language_server_data() && pos.has_value()) { |
| const StructType* type = *reference.temporary().type()->StructSupertype(); |
| const Field& field = type->LookupField(fieldname); |
| LanguageServerData::AddDefinition(*pos, field.pos); |
| } |
| return LocationReference::Temporary( |
| ProjectStructField(reference.temporary(), fieldname), |
| reference.temporary_description()); |
| } |
| if (base::Optional<const Type*> referenced_type = |
| reference.ReferencedType()) { |
| if ((*referenced_type)->IsBitFieldStructType()) { |
| const BitFieldStructType* bitfield_struct = |
| BitFieldStructType::cast(*referenced_type); |
| const BitField& field = bitfield_struct->LookupField(fieldname); |
| return LocationReference::BitFieldAccess(reference, field); |
| } |
| if (const auto type_wrapped_in_smi = Type::MatchUnaryGeneric( |
| (*referenced_type), TypeOracle::GetSmiTaggedGeneric())) { |
| const BitFieldStructType* bitfield_struct = |
| BitFieldStructType::DynamicCast(*type_wrapped_in_smi); |
| if (bitfield_struct == nullptr) { |
| ReportError( |
| "When a value of type SmiTagged<T> is used in a field access " |
| "expression, T is expected to be a bitfield struct type. Instead, " |
| "T " |
| "is ", |
| **type_wrapped_in_smi); |
| } |
| const BitField& field = bitfield_struct->LookupField(fieldname); |
| return LocationReference::BitFieldAccess(reference, field); |
| } |
| } |
| if (reference.IsHeapReference()) { |
| VisitResult ref = reference.heap_reference(); |
| bool is_const; |
| auto generic_type = |
| TypeOracle::MatchReferenceGeneric(ref.type(), &is_const); |
| if (!generic_type) { |
| ReportError( |
| "Left-hand side of field access expression is marked as a reference " |
| "but is not of type Reference<...>. Found type: ", |
| ref.type()->ToString()); |
| } |
| if (auto struct_type = (*generic_type)->StructSupertype()) { |
| const Field& field = (*struct_type)->LookupField(fieldname); |
| // Update the Reference's type to refer to the field type within the |
| // struct. |
| ref.SetType(TypeOracle::GetReferenceType( |
| field.name_and_type.type, |
| is_const || |
| (field.const_qualified && !ignore_stuct_field_constness))); |
| if (!field.offset.has_value()) { |
| Error("accessing field with unknown offset").Throw(); |
| } |
| if (*field.offset != 0) { |
| // Copy the Reference struct up the stack and update the new copy's |
| // |offset| value to point to the struct field. |
| StackScope scope(this); |
| ref = GenerateCopy(ref); |
| VisitResult ref_offset = ProjectStructField(ref, "offset"); |
| VisitResult struct_offset{ |
| TypeOracle::GetIntPtrType()->ConstexprVersion(), |
| std::to_string(*field.offset)}; |
| VisitResult updated_offset = |
| GenerateCall("+", Arguments{{ref_offset, struct_offset}, {}}); |
| assembler().Poke(ref_offset.stack_range(), updated_offset.stack_range(), |
| ref_offset.type()); |
| ref = scope.Yield(ref); |
| } |
| return LocationReference::HeapReference(ref); |
| } |
| } |
| VisitResult object_result = GenerateFetchFromLocation(reference); |
| if (base::Optional<const ClassType*> class_type = |
| object_result.type()->ClassSupertype()) { |
| // This is a hack to distinguish the situation where we want to use |
| // overloaded field accessors from when we want to create a reference. |
| bool has_explicit_overloads = TestLookupCallable( |
| QualifiedName{"." + fieldname}, {object_result.type()}); |
| if ((*class_type)->HasField(fieldname) && !has_explicit_overloads) { |
| const Field& field = (*class_type)->LookupField(fieldname); |
| if (GlobalContext::collect_language_server_data() && pos.has_value()) { |
| LanguageServerData::AddDefinition(*pos, field.pos); |
| } |
| return GenerateFieldReference(object_result, field, *class_type); |
| } |
| } |
| return LocationReference::FieldAccess(object_result, fieldname); |
| } |
| |
| LocationReference ImplementationVisitor::GetLocationReference( |
| ElementAccessExpression* expr) { |
| LocationReference reference = GetLocationReference(expr->array); |
| VisitResult index = Visit(expr->index); |
| if (reference.IsHeapSlice()) { |
| Arguments arguments{{index}, {}}; |
| const AggregateType* slice_type = |
| AggregateType::cast(reference.heap_slice().type()); |
| Method* method = LookupMethod("AtIndex", slice_type, arguments, {}); |
| // The reference has to be treated like a normal value when calling methods |
| // on the underlying slice implementation. |
| LocationReference slice_value = LocationReference::Temporary( |
| reference.GetVisitResult(), "slice as value"); |
| return LocationReference::HeapReference( |
| GenerateCall(method, std::move(slice_value), arguments, {}, false)); |
| } else { |
| return LocationReference::ArrayAccess(GenerateFetchFromLocation(reference), |
| index); |
| } |
| } |
| |
| LocationReference ImplementationVisitor::GetLocationReference( |
| IdentifierExpression* expr) { |
| if (expr->namespace_qualification.empty()) { |
| if (base::Optional<Binding<LocalValue>*> value = |
| TryLookupLocalValue(expr->name->value)) { |
| if (GlobalContext::collect_language_server_data()) { |
| LanguageServerData::AddDefinition(expr->name->pos, |
| (*value)->declaration_position()); |
| } |
| if (expr->generic_arguments.size() != 0) { |
| ReportError("cannot have generic parameters on local name ", |
| expr->name); |
| } |
| return (*value)->GetLocationReference(*value); |
| } |
| } |
| |
| if (expr->IsThis()) { |
| ReportError("\"this\" cannot be qualified"); |
| } |
| QualifiedName name = |
| QualifiedName(expr->namespace_qualification, expr->name->value); |
| if (base::Optional<Builtin*> builtin = Declarations::TryLookupBuiltin(name)) { |
| if (GlobalContext::collect_language_server_data()) { |
| LanguageServerData::AddDefinition(expr->name->pos, |
| (*builtin)->Position()); |
| } |
| return LocationReference::Temporary(GetBuiltinCode(*builtin), |
| "builtin " + expr->name->value); |
| } |
| if (expr->generic_arguments.size() != 0) { |
| GenericCallable* generic = Declarations::LookupUniqueGeneric(name); |
| Callable* specialization = |
| GetOrCreateSpecialization(SpecializationKey<GenericCallable>{ |
| generic, TypeVisitor::ComputeTypeVector(expr->generic_arguments)}); |
| if (Builtin* builtin = Builtin::DynamicCast(specialization)) { |
| DCHECK(!builtin->IsExternal()); |
| return LocationReference::Temporary(GetBuiltinCode(builtin), |
| "builtin " + expr->name->value); |
| } else { |
| ReportError("cannot create function pointer for non-builtin ", |
| generic->name()); |
| } |
| } |
| Value* value = Declarations::LookupValue(name); |
| if (GlobalContext::collect_language_server_data()) { |
| LanguageServerData::AddDefinition(expr->name->pos, value->name()->pos); |
| } |
| if (auto* constant = NamespaceConstant::DynamicCast(value)) { |
| if (constant->type()->IsConstexpr()) { |
| return LocationReference::Temporary( |
| VisitResult(constant->type(), constant->external_name() + "(state_)"), |
| "namespace constant " + expr->name->value); |
| } |
| assembler().Emit(NamespaceConstantInstruction{constant}); |
| StackRange stack_range = |
| assembler().TopRange(LoweredSlotCount(constant->type())); |
| return LocationReference::Temporary( |
| VisitResult(constant->type(), stack_range), |
| "namespace constant " + expr->name->value); |
| } |
| ExternConstant* constant = ExternConstant::cast(value); |
| return LocationReference::Temporary(constant->value(), |
| "extern value " + expr->name->value); |
| } |
| |
| LocationReference ImplementationVisitor::GetLocationReference( |
| DereferenceExpression* expr) { |
| VisitResult ref = Visit(expr->reference); |
| if (!TypeOracle::MatchReferenceGeneric(ref.type())) { |
| Error("Operator * expects a reference type but found a value of type ", |
| *ref.type()) |
| .Throw(); |
| } |
| return LocationReference::HeapReference(ref); |
| } |
| |
| VisitResult ImplementationVisitor::GenerateFetchFromLocation( |
| const LocationReference& reference) { |
| if (reference.IsTemporary()) { |
| return GenerateCopy(reference.temporary()); |
| } else if (reference.IsVariableAccess()) { |
| return GenerateCopy(reference.variable()); |
| } else if (reference.IsHeapReference()) { |
| const Type* referenced_type = *reference.ReferencedType(); |
| if (referenced_type == TypeOracle::GetFloat64OrHoleType()) { |
| return GenerateCall(QualifiedName({TORQUE_INTERNAL_NAMESPACE_STRING}, |
| "LoadFloat64OrHole"), |
| Arguments{{reference.heap_reference()}, {}}); |
| } else if (auto struct_type = referenced_type->StructSupertype()) { |
| StackRange result_range = assembler().TopRange(0); |
| for (const Field& field : (*struct_type)->fields()) { |
| StackScope scope(this); |
| const std::string& fieldname = field.name_and_type.name; |
| VisitResult field_value = scope.Yield(GenerateFetchFromLocation( |
| GenerateFieldAccess(reference, fieldname))); |
| result_range.Extend(field_value.stack_range()); |
| } |
| return VisitResult(referenced_type, result_range); |
| } else { |
| GenerateCopy(reference.heap_reference()); |
| assembler().Emit(LoadReferenceInstruction{referenced_type}); |
| DCHECK_EQ(1, LoweredSlotCount(referenced_type)); |
| return VisitResult(referenced_type, assembler().TopRange(1)); |
| } |
| } else if (reference.IsBitFieldAccess()) { |
| // First fetch the bitfield struct, then get the bits out of it. |
| VisitResult bit_field_struct = |
| GenerateFetchFromLocation(reference.bit_field_struct_location()); |
| assembler().Emit(LoadBitFieldInstruction{bit_field_struct.type(), |
| reference.bit_field()}); |
| return VisitResult(*reference.ReferencedType(), assembler().TopRange(1)); |
| } else { |
| if (reference.IsHeapSlice()) { |
| ReportError( |
| "fetching a value directly from an indexed field isn't allowed"); |
| } |
| DCHECK(reference.IsCallAccess()); |
| return GenerateCall(reference.eval_function(), |
| Arguments{reference.call_arguments(), {}}); |
| } |
| } |
| |
| void ImplementationVisitor::GenerateAssignToLocation( |
| const LocationReference& reference, const VisitResult& assignment_value) { |
| if (reference.IsCallAccess()) { |
| Arguments arguments{reference.call_arguments(), {}}; |
| arguments.parameters.push_back(assignment_value); |
| GenerateCall(reference.assign_function(), arguments); |
| } else if (reference.IsVariableAccess()) { |
| VisitResult variable = reference.variable(); |
| VisitResult converted_value = |
| GenerateImplicitConvert(variable.type(), assignment_value); |
| assembler().Poke(variable.stack_range(), converted_value.stack_range(), |
| variable.type()); |
| |
| // Local variables are detected by the existence of a binding. Assignment |
| // to local variables is recorded to support lint errors. |
| if (reference.binding()) { |
| (*reference.binding())->SetWritten(); |
| } |
| } else if (reference.IsHeapSlice()) { |
| ReportError("assigning a value directly to an indexed field isn't allowed"); |
| } else if (reference.IsHeapReference()) { |
| const Type* referenced_type = *reference.ReferencedType(); |
| if (reference.IsConst()) { |
| Error("cannot assign to const value of type ", *referenced_type).Throw(); |
| } |
| if (referenced_type == TypeOracle::GetFloat64OrHoleType()) { |
| GenerateCall( |
| QualifiedName({TORQUE_INTERNAL_NAMESPACE_STRING}, |
| "StoreFloat64OrHole"), |
| Arguments{{reference.heap_reference(), assignment_value}, {}}); |
| } else if (auto struct_type = referenced_type->StructSupertype()) { |
| if (!assignment_value.type()->IsSubtypeOf(referenced_type)) { |
| ReportError("Cannot assign to ", *referenced_type, |
| " with value of type ", *assignment_value.type()); |
| } |
| for (const Field& field : (*struct_type)->fields()) { |
| const std::string& fieldname = field.name_and_type.name; |
| // Allow assignment of structs even if they contain const fields. |
| // Const on struct fields just disallows direct writes to them. |
| bool ignore_stuct_field_constness = true; |
| GenerateAssignToLocation( |
| GenerateFieldAccess(reference, fieldname, |
| ignore_stuct_field_constness), |
| ProjectStructField(assignment_value, fieldname)); |
| } |
| } else { |
| GenerateCopy(reference.heap_reference()); |
| VisitResult converted_assignment_value = |
| GenerateImplicitConvert(referenced_type, assignment_value); |
| if (referenced_type == TypeOracle::GetFloat64Type()) { |
| VisitResult silenced_float_value = GenerateCall( |
| "Float64SilenceNaN", Arguments{{assignment_value}, {}}); |
| assembler().Poke(converted_assignment_value.stack_range(), |
| silenced_float_value.stack_range(), referenced_type); |
| } |
| assembler().Emit(StoreReferenceInstruction{referenced_type}); |
| } |
| } else if (reference.IsBitFieldAccess()) { |
| // First fetch the bitfield struct, then set the updated bits, then store |
| // it back to where we found it. |
| VisitResult bit_field_struct = |
| GenerateFetchFromLocation(reference.bit_field_struct_location()); |
| VisitResult converted_value = |
| GenerateImplicitConvert(*reference.ReferencedType(), assignment_value); |
| VisitResult updated_bit_field_struct = |
| GenerateSetBitField(bit_field_struct.type(), reference.bit_field(), |
| bit_field_struct, converted_value); |
| GenerateAssignToLocation(reference.bit_field_struct_location(), |
| updated_bit_field_struct); |
| } else { |
| DCHECK(reference.IsTemporary()); |
| ReportError("cannot assign to const-bound or temporary ", |
| reference.temporary_description()); |
| } |
| } |
| |
| VisitResult ImplementationVisitor::GeneratePointerCall( |
| Expression* callee, const Arguments& arguments, bool is_tailcall) { |
| StackScope scope(this); |
| TypeVector parameter_types(arguments.parameters.ComputeTypeVector()); |
| VisitResult callee_result = Visit(callee); |
| if (!callee_result.type()->IsBuiltinPointerType()) { |
| std::stringstream stream; |
| stream << "Expected a function pointer type but found " |
| << *callee_result.type(); |
| ReportError(stream.str()); |
| } |
| const BuiltinPointerType* type = |
| BuiltinPointerType::cast(callee_result.type()); |
| |
| if (type->parameter_types().size() != parameter_types.size()) { |
| std::stringstream stream; |
| stream << "parameter count mismatch calling function pointer with Type: " |
| << *type << " - expected " |
| << std::to_string(type->parameter_types().size()) << ", found " |
| << std::to_string(parameter_types.size()); |
| ReportError(stream.str()); |
| } |
| |
| ParameterTypes types{type->parameter_types(), false}; |
| Signature sig; |
| sig.parameter_types = types; |
| if (!IsCompatibleSignature(sig, parameter_types, 0)) { |
| std::stringstream stream; |
| stream << "parameters do not match function pointer signature. Expected: (" |
| << type->parameter_types() << ") but got: (" << parameter_types |
| << ")"; |
| ReportError(stream.str()); |
| } |
| |
| callee_result = GenerateCopy(callee_result); |
| StackRange arg_range = assembler().TopRange(0); |
| for (size_t current = 0; current < arguments.parameters.size(); ++current) { |
| const Type* to_type = type->parameter_types()[current]; |
| arg_range.Extend( |
| GenerateImplicitConvert(to_type, arguments.parameters[current]) |
| .stack_range()); |
| } |
| |
| assembler().Emit( |
| CallBuiltinPointerInstruction{is_tailcall, type, arg_range.Size()}); |
| |
| if (is_tailcall) { |
| return VisitResult::NeverResult(); |
| } |
| DCHECK_EQ(1, LoweredSlotCount(type->return_type())); |
| return scope.Yield(VisitResult(type->return_type(), assembler().TopRange(1))); |
| } |
| |
| void ImplementationVisitor::AddCallParameter( |
| Callable* callable, VisitResult parameter, const Type* parameter_type, |
| std::vector<VisitResult>* converted_arguments, StackRange* argument_range, |
| std::vector<std::string>* constexpr_arguments, bool inline_macro) { |
| VisitResult converted; |
| if ((converted_arguments->size() < callable->signature().implicit_count) && |
| parameter.type()->IsTopType()) { |
| converted = GenerateCopy(parameter); |
| } else { |
| converted = GenerateImplicitConvert(parameter_type, parameter); |
| } |
| converted_arguments->push_back(converted); |
| if (!inline_macro) { |
| if (converted.IsOnStack()) { |
| argument_range->Extend(converted.stack_range()); |
| } else { |
| constexpr_arguments->push_back(converted.constexpr_value()); |
| } |
| } |
| } |
| |
| namespace { |
| std::pair<std::string, std::string> GetClassInstanceTypeRange( |
| const ClassType* class_type) { |
| std::pair<std::string, std::string> result; |
| if (class_type->InstanceTypeRange()) { |
| auto instance_type_range = *class_type->InstanceTypeRange(); |
| std::string instance_type_string_first = |
| "static_cast<InstanceType>(" + |
| std::to_string(instance_type_range.first) + ")"; |
| std::string instance_type_string_second = |
| "static_cast<InstanceType>(" + |
| std::to_string(instance_type_range.second) + ")"; |
| result = |
| std::make_pair(instance_type_string_first, instance_type_string_second); |
| } else { |
| ReportError( |
| "%Min/MaxInstanceType must take a class type that is either a string " |
| "or has a generated instance type range"); |
| } |
| return result; |
| } |
| } // namespace |
| |
| VisitResult ImplementationVisitor::GenerateCall( |
| Callable* callable, base::Optional<LocationReference> this_reference, |
| Arguments arguments, const TypeVector& specialization_types, |
| bool is_tailcall) { |
| const Type* return_type = callable->signature().return_type; |
| |
| if (is_tailcall) { |
| if (Builtin* builtin = Builtin::DynamicCast(CurrentCallable::Get())) { |
| const Type* outer_return_type = builtin->signature().return_type; |
| if (!return_type->IsSubtypeOf(outer_return_type)) { |
| Error("Cannot tailcall, type of result is ", *return_type, |
| " but should be a subtype of ", *outer_return_type, "."); |
| } |
| } else { |
| Error("Tail calls are only allowed from builtins"); |
| } |
| } |
| |
| bool inline_macro = callable->ShouldBeInlined(output_type_); |
| std::vector<VisitResult> implicit_arguments; |
| for (size_t i = 0; i < callable->signature().implicit_count; ++i) { |
| std::string implicit_name = callable->signature().parameter_names[i]->value; |
| base::Optional<Binding<LocalValue>*> val = |
| TryLookupLocalValue(implicit_name); |
| if (val) { |
| implicit_arguments.push_back( |
| GenerateFetchFromLocation((*val)->GetLocationReference(*val))); |
| } else { |
| VisitResult unititialized = VisitResult::TopTypeResult( |
| "implicit parameter '" + implicit_name + |
| "' is not defined when invoking " + callable->ReadableName() + |
| " at " + PositionAsString(CurrentSourcePosition::Get()), |
| callable->signature().parameter_types.types[i]); |
| implicit_arguments.push_back(unititialized); |
| } |
| const Type* type = implicit_arguments.back().type(); |
| if (const TopType* top_type = TopType::DynamicCast(type)) { |
| if (!callable->IsMacro() || callable->IsExternal()) { |
| ReportError( |
| "unititialized implicit parameters can only be passed to " |
| "Torque-defined macros: the ", |
| top_type->reason()); |
| } |
| inline_macro = true; |
| } |
| } |
| |
| std::vector<VisitResult> converted_arguments; |
| StackRange argument_range = assembler().TopRange(0); |
| std::vector<std::string> constexpr_arguments; |
| |
| size_t current = 0; |
| for (; current < callable->signature().implicit_count; ++current) { |
| AddCallParameter(callable, implicit_arguments[current], |
| callable->signature().parameter_types.types[current], |
| &converted_arguments, &argument_range, |
| &constexpr_arguments, inline_macro); |
| } |
| |
| if (this_reference) { |
| DCHECK(callable->IsMethod()); |
| Method* method = Method::cast(callable); |
| // By now, the this reference should either be a variable, a temporary or |
| // a Slice. In either case the fetch of the VisitResult should succeed. |
| VisitResult this_value = this_reference->GetVisitResult(); |
| if (inline_macro) { |
| if (!this_value.type()->IsSubtypeOf(method->aggregate_type())) { |
| ReportError("this parameter must be a subtype of ", |
| *method->aggregate_type(), " but it is of type ", |
| this_value.type()); |
| } |
| } else { |
| AddCallParameter(callable, this_value, method->aggregate_type(), |
| &converted_arguments, &argument_range, |
| &constexpr_arguments, inline_macro); |
| } |
| ++current; |
| } |
| |
| for (auto arg : arguments.parameters) { |
| const Type* to_type = (current >= callable->signature().types().size()) |
| ? TypeOracle::GetObjectType() |
| : callable->signature().types()[current++]; |
| AddCallParameter(callable, arg, to_type, &converted_arguments, |
| &argument_range, &constexpr_arguments, inline_macro); |
| } |
| |
| size_t label_count = callable->signature().labels.size(); |
| if (label_count != arguments.labels.size()) { |
| std::stringstream s; |
| s << "unexpected number of otherwise labels for " |
| << callable->ReadableName() << " (expected " |
| << std::to_string(label_count) << " found " |
| << std::to_string(arguments.labels.size()) << ")"; |
| ReportError(s.str()); |
| } |
| |
| if (callable->IsTransitioning()) { |
| if (!CurrentCallable::Get()->IsTransitioning()) { |
| std::stringstream s; |
| s << *CurrentCallable::Get() |
| << " isn't marked transitioning but calls the transitioning " |
| << *callable; |
| ReportError(s.str()); |
| } |
| } |
| |
| if (auto* builtin = Builtin::DynamicCast(callable)) { |
| base::Optional<Block*> catch_block = GetCatchBlock(); |
| assembler().Emit(CallBuiltinInstruction{ |
| is_tailcall, builtin, argument_range.Size(), catch_block}); |
| GenerateCatchBlock(catch_block); |
| if (is_tailcall) { |
| return VisitResult::NeverResult(); |
| } else { |
| size_t slot_count = LoweredSlotCount(return_type); |
| DCHECK_LE(slot_count, 1); |
| // TODO(tebbi): Actually, builtins have to return a value, so we should |
| // assert slot_count == 1 here. |
| return VisitResult(return_type, assembler().TopRange(slot_count)); |
| } |
| } else if (auto* macro = Macro::DynamicCast(callable)) { |
| if (is_tailcall) { |
| ReportError("can't tail call a macro"); |
| } |
| |
| macro->SetUsed(); |
| |
| // If we're currently generating a C++ macro and it's calling another macro, |
| // then we need to make sure that we also generate C++ code for the called |
| // macro. |
| if (output_type_ == OutputType::kCC && !inline_macro) { |
| if (auto* torque_macro = TorqueMacro::DynamicCast(macro)) { |
| GlobalContext::EnsureInCCOutputList(torque_macro); |
| } |
| } |
| |
| if (return_type->IsConstexpr()) { |
| DCHECK_EQ(0, arguments.labels.size()); |
| std::stringstream result; |
| result << "("; |
| bool first = true; |
| if (auto* extern_macro = ExternMacro::DynamicCast(macro)) { |
| result << extern_macro->external_assembler_name() << "(state_)." |
| << extern_macro->ExternalName() << "("; |
| } else { |
| result << macro->ExternalName() << "(state_"; |
| first = false; |
| } |
| for (VisitResult arg : arguments.parameters) { |
| DCHECK(!arg.IsOnStack()); |
| if (!first) { |
| result << ", "; |
| } |
| first = false; |
| result << arg.constexpr_value(); |
| } |
| result << "))"; |
| return VisitResult(return_type, result.str()); |
| } else if (inline_macro) { |
| std::vector<Block*> label_blocks; |
| for (Binding<LocalLabel>* label : arguments.labels) { |
| label_blocks.push_back(label->block); |
| } |
| return InlineMacro(macro, this_reference, converted_arguments, |
| label_blocks); |
| } else if (arguments.labels.empty() && |
| return_type != TypeOracle::GetNeverType()) { |
| base::Optional<Block*> catch_block = GetCatchBlock(); |
| assembler().Emit( |
| CallCsaMacroInstruction{macro, constexpr_arguments, catch_block}); |
| GenerateCatchBlock(catch_block); |
| size_t return_slot_count = LoweredSlotCount(return_type); |
| return VisitResult(return_type, assembler().TopRange(return_slot_count)); |
| } else { |
| base::Optional<Block*> return_continuation; |
| if (return_type != TypeOracle::GetNeverType()) { |
| return_continuation = assembler().NewBlock(); |
| } |
| |
| std::vector<Block*> label_blocks; |
| |
| for (size_t i = 0; i < label_count; ++i) { |
| label_blocks.push_back(assembler().NewBlock()); |
| } |
| base::Optional<Block*> catch_block = GetCatchBlock(); |
| assembler().Emit(CallCsaMacroAndBranchInstruction{ |
| macro, constexpr_arguments, return_continuation, label_blocks, |
| catch_block}); |
| GenerateCatchBlock(catch_block); |
| |
| for (size_t i = 0; i < label_count; ++i) { |
| Binding<LocalLabel>* label = arguments.labels[i]; |
| size_t callee_label_parameters = |
| callable->signature().labels[i].types.size(); |
| if (label->parameter_types.size() != callee_label_parameters) { |
| std::stringstream s; |
| s << "label " << label->name() |
| << " doesn't have the right number of parameters (found " |
| << std::to_string(label->parameter_types.size()) << " expected " |
| << std::to_string(callee_label_parameters) << ")"; |
| ReportError(s.str()); |
| } |
| assembler().Bind(label_blocks[i]); |
| assembler().Goto( |
| label->block, |
| LowerParameterTypes(callable->signature().labels[i].types).size()); |
| |
| size_t j = 0; |
| for (auto t : callable->signature().labels[i].types) { |
| const Type* parameter_type = label->parameter_types[j]; |
| if (!t->IsSubtypeOf(parameter_type)) { |
| ReportError("mismatch of label parameters (label expects ", |
| *parameter_type, " but macro produces ", *t, |
| " for parameter ", i + 1, ")"); |
| } |
| j++; |
| } |
| } |
| |
| if (return_continuation) { |
| assembler().Bind(*return_continuation); |
| size_t return_slot_count = LoweredSlotCount(return_type); |
| return VisitResult(return_type, |
| assembler().TopRange(return_slot_count)); |
| } else { |
| return VisitResult::NeverResult(); |
| } |
| } |
| } else if (auto* runtime_function = RuntimeFunction::DynamicCast(callable)) { |
| base::Optional<Block*> catch_block = GetCatchBlock(); |
| assembler().Emit(CallRuntimeInstruction{ |
| is_tailcall, runtime_function, argument_range.Size(), catch_block}); |
| GenerateCatchBlock(catch_block); |
| if (is_tailcall || return_type == TypeOracle::GetNeverType()) { |
| return VisitResult::NeverResult(); |
| } else { |
| size_t slot_count = LoweredSlotCount(return_type); |
| DCHECK_LE(slot_count, 1); |
| // TODO(tebbi): Actually, runtime functions have to return a value, so |
| // we should assert slot_count == 1 here. |
| return VisitResult(return_type, assembler().TopRange(slot_count)); |
| } |
| } else if (auto* intrinsic = Intrinsic::DynamicCast(callable)) { |
| if (intrinsic->ExternalName() == "%SizeOf") { |
| if (specialization_types.size() != 1) { |
| ReportError("%SizeOf must take a single type parameter"); |
| } |
| const Type* type = specialization_types[0]; |
| std::string size_string; |
| if (base::Optional<std::tuple<size_t, std::string>> size = SizeOf(type)) { |
| size_string = std::get<1>(*size); |
| } else { |
| Error("size of ", *type, " is not known."); |
| } |
| return VisitResult(return_type, size_string); |
| } else if (intrinsic->ExternalName() == "%ClassHasMapConstant") { |
| const Type* type = specialization_types[0]; |
| const ClassType* class_type = ClassType::DynamicCast(type); |
| if (!class_type) { |
| ReportError("%ClassHasMapConstant must take a class type parameter"); |
| } |
| // If the class isn't actually used as the parameter to a TNode, |
| // then we can't rely on the class existing in C++ or being of the same |
| // type (e.g. it could be a template), so don't use the template CSA |
| // machinery for accessing the class' map. |
| if (class_type->name() != class_type->GetGeneratedTNodeTypeName()) { |
| return VisitResult(return_type, std::string("false")); |
| } else { |
| return VisitResult( |
| return_type, |
| std::string("CodeStubAssembler(state_).ClassHasMapConstant<") + |
| class_type->name() + ">()"); |
| } |
| } else if (intrinsic->ExternalName() == "%MinInstanceType") { |
| if (specialization_types.size() != 1) { |
| ReportError("%MinInstanceType must take a single type parameter"); |
| } |
| const Type* type = specialization_types[0]; |
| const ClassType* class_type = ClassType::DynamicCast(type); |
| if (!class_type) { |
| ReportError("%MinInstanceType must take a class type parameter"); |
| } |
| std::pair<std::string, std::string> instance_types = |
| GetClassInstanceTypeRange(class_type); |
| return VisitResult(return_type, instance_types.first); |
| } else if (intrinsic->ExternalName() == "%MaxInstanceType") { |
| if (specialization_types.size() != 1) { |
| ReportError("%MaxInstanceType must take a single type parameter"); |
| } |
| const Type* type = specialization_types[0]; |
| const ClassType* class_type = ClassType::DynamicCast(type); |
| if (!class_type) { |
| ReportError("%MaxInstanceType must take a class type parameter"); |
| } |
| std::pair<std::string, std::string> instance_types = |
| GetClassInstanceTypeRange(class_type); |
| return VisitResult(return_type, instance_types.second); |
| } else if (intrinsic->ExternalName() == "%RawConstexprCast") { |
| if (intrinsic->signature().parameter_types.types.size() != 1 || |
| constexpr_arguments.size() != 1) { |
| ReportError( |
| "%RawConstexprCast must take a single parameter with constexpr " |
| "type"); |
| } |
| if (!return_type->IsConstexpr()) { |
| std::stringstream s; |
| s << *return_type |
| << " return type for %RawConstexprCast is not constexpr"; |
| ReportError(s.str()); |
| } |
| std::stringstream result; |
| result << "static_cast<" << return_type->GetGeneratedTypeName() << ">("; |
| result << constexpr_arguments[0]; |
| result << ")"; |
| return VisitResult(return_type, result.str()); |
| } else if (intrinsic->ExternalName() == "%IndexedFieldLength") { |
| const Type* type = specialization_types[0]; |
| const ClassType* class_type = ClassType::DynamicCast(type); |
| if (!class_type) { |
| ReportError("%IndexedFieldLength must take a class type parameter"); |
| } |
| const Field& field = |
| class_type->LookupField(StringLiteralUnquote(constexpr_arguments[0])); |
| return GenerateArrayLength(VisitResult(type, argument_range), field); |
| } else { |
| assembler().Emit(CallIntrinsicInstruction{intrinsic, specialization_types, |
| constexpr_arguments}); |
| size_t return_slot_count = |
| LoweredSlotCount(intrinsic->signature().return_type); |
| return VisitResult(return_type, assembler().TopRange(return_slot_count)); |
| } |
| } else { |
| UNREACHABLE(); |
| } |
| } |
| |
| VisitResult ImplementationVisitor::GenerateCall( |
| const QualifiedName& callable_name, Arguments arguments, |
| const TypeVector& specialization_types, bool is_tailcall) { |
| Callable* callable = |
| LookupCallable(callable_name, Declarations::Lookup(callable_name), |
| arguments, specialization_types); |
| return GenerateCall(callable, base::nullopt, arguments, specialization_types, |
| is_tailcall); |
| } |
| |
| VisitResult ImplementationVisitor::Visit(CallExpression* expr, |
| bool is_tailcall) { |
| StackScope scope(this); |
| |
| if (expr->callee->name->value == "&" && expr->arguments.size() == 1) { |
| if (auto* loc_expr = LocationExpression::DynamicCast(expr->arguments[0])) { |
| LocationReference ref = GetLocationReference(loc_expr); |
| if (ref.IsHeapReference()) return scope.Yield(ref.heap_reference()); |
| if (ref.IsHeapSlice()) return scope.Yield(ref.heap_slice()); |
| } |
| ReportError("Unable to create a heap reference."); |
| } |
| |
| Arguments arguments; |
| QualifiedName name = QualifiedName(expr->callee->namespace_qualification, |
| expr->callee->name->value); |
| TypeVector specialization_types = |
| TypeVisitor::ComputeTypeVector(expr->callee->generic_arguments); |
| bool has_template_arguments = !specialization_types.empty(); |
| for (Expression* arg : expr->arguments) |
| arguments.parameters.push_back(Visit(arg)); |
| arguments.labels = LabelsFromIdentifiers(expr->labels); |
| if (!has_template_arguments && name.namespace_qualification.empty() && |
| TryLookupLocalValue(name.name)) { |
| return scope.Yield( |
| GeneratePointerCall(expr->callee, arguments, is_tailcall)); |
| } else { |
| if (GlobalContext::collect_language_server_data()) { |
| Callable* callable = LookupCallable(name, Declarations::Lookup(name), |
| arguments, specialization_types); |
| LanguageServerData::AddDefinition(expr->callee->name->pos, |
| callable->IdentifierPosition()); |
| } |
| if (expr->callee->name->value == "!" && arguments.parameters.size() == 1) { |
| PropagateBitfieldMark(expr->arguments[0], expr); |
| } |
| if (expr->callee->name->value == "==" && arguments.parameters.size() == 2) { |
| if (arguments.parameters[0].type()->IsConstexpr()) { |
| PropagateBitfieldMark(expr->arguments[1], expr); |
| } else if (arguments.parameters[1].type()->IsConstexpr()) { |
| PropagateBitfieldMark(expr->arguments[0], expr); |
| } |
| } |
| return scope.Yield( |
| GenerateCall(name, arguments, specialization_types, is_tailcall)); |
| } |
| } |
| |
| VisitResult ImplementationVisitor::Visit(CallMethodExpression* expr) { |
| StackScope scope(this); |
| Arguments arguments; |
| std::string method_name = expr->method->name->value; |
| TypeVector specialization_types = |
| TypeVisitor::ComputeTypeVector(expr->method->generic_arguments); |
| LocationReference target = GetLocationReference(expr->target); |
| if (!target.IsVariableAccess()) { |
| VisitResult result = GenerateFetchFromLocation(target); |
| target = LocationReference::Temporary(result, "this parameter"); |
| } |
| const AggregateType* target_type = |
| AggregateType::DynamicCast(*target.ReferencedType()); |
| if (!target_type) { |
| ReportError("target of method call not a struct or class type"); |
| } |
| for (Expression* arg : expr->arguments) { |
| arguments.parameters.push_back(Visit(arg)); |
| } |
| arguments.labels = LabelsFromIdentifiers(expr->labels); |
| TypeVector argument_types = arguments.parameters.ComputeTypeVector(); |
| DCHECK_EQ(expr->method->namespace_qualification.size(), 0); |
| QualifiedName qualified_name = QualifiedName(method_name); |
| Callable* callable = nullptr; |
| callable = LookupMethod(method_name, target_type, arguments, {}); |
| if (GlobalContext::collect_language_server_data()) { |
| LanguageServerData::AddDefinition(expr->method->name->pos, |
| callable->IdentifierPosition()); |
| } |
| return scope.Yield(GenerateCall(callable, target, arguments, {}, false)); |
| } |
| |
| VisitResult ImplementationVisitor::Visit(IntrinsicCallExpression* expr) { |
| StackScope scope(this); |
| Arguments arguments; |
| TypeVector specialization_types = |
| TypeVisitor::ComputeTypeVector(expr->generic_arguments); |
| for (Expression* arg : expr->arguments) |
| arguments.parameters.push_back(Visit(arg)); |
| return scope.Yield( |
| GenerateCall(expr->name->value, arguments, specialization_types, false)); |
| } |
| |
| void ImplementationVisitor::GenerateBranch(const VisitResult& condition, |
| Block* true_block, |
| Block* false_block) { |
| DCHECK_EQ(condition, |
| VisitResult(TypeOracle::GetBoolType(), assembler().TopRange(1))); |
| assembler().Branch(true_block, false_block); |
| } |
| |
| VisitResult ImplementationVisitor::GenerateBoolConstant(bool constant) { |
| return GenerateImplicitConvert(TypeOracle::GetBoolType(), |
| VisitResult(TypeOracle::GetConstexprBoolType(), |
| constant ? "true" : "false")); |
| } |
| |
| void ImplementationVisitor::GenerateExpressionBranch(Expression* expression, |
| Block* true_block, |
| Block* false_block) { |
| StackScope stack_scope(this); |
| VisitResult expression_result = this->Visit(expression); |
| expression_result = stack_scope.Yield( |
| GenerateImplicitConvert(TypeOracle::GetBoolType(), expression_result)); |
| GenerateBranch(expression_result, true_block, false_block); |
| } |
| |
| VisitResult ImplementationVisitor::GenerateImplicitConvert( |
| const Type* destination_type, VisitResult source) { |
| StackScope scope(this); |
| if (source.type() == TypeOracle::GetNeverType()) { |
| ReportError("it is not allowed to use a value of type never"); |
| } |
| |
| if (destination_type == source.type()) { |
| return scope.Yield(GenerateCopy(source)); |
| } |
| |
| if (auto from = TypeOracle::ImplicitlyConvertableFrom(destination_type, |
| source.type())) { |
| return scope.Yield(GenerateCall(kFromConstexprMacroName, |
| Arguments{{source}, {}}, |
| {destination_type, *from}, false)); |
| } else if (IsAssignableFrom(destination_type, source.type())) { |
| source.SetType(destination_type); |
| return scope.Yield(GenerateCopy(source)); |
| } else { |
| std::stringstream s; |
| if (const TopType* top_type = TopType::DynamicCast(source.type())) { |
| s << "undefined expression of type " << *destination_type << ": the " |
| << top_type->reason(); |
| } else { |
| s << "cannot use expression of type " << *source.type() |
| << " as a value of type " << *destination_type; |
| } |
| ReportError(s.str()); |
| } |
| } |
| |
| StackRange ImplementationVisitor::GenerateLabelGoto( |
| LocalLabel* label, base::Optional<StackRange> arguments) { |
| return assembler().Goto(label->block, arguments ? arguments->Size() : 0); |
| } |
| |
| std::vector<Binding<LocalLabel>*> ImplementationVisitor::LabelsFromIdentifiers( |
| const std::vector<Identifier*>& names) { |
| std::vector<Binding<LocalLabel>*> result; |
| result.reserve(names.size()); |
| for (const auto& name : names) { |
| Binding<LocalLabel>* label = LookupLabel(name->value); |
| result.push_back(label); |
| |
| // Link up labels in "otherwise" part of the call expression with |
| // either the label in the signature of the calling macro or the label |
| // block ofa surrounding "try". |
| if (GlobalContext::collect_language_server_data()) { |
| LanguageServerData::AddDefinition(name->pos, |
| label->declaration_position()); |
| } |
| } |
| return result; |
| } |
| |
| StackRange ImplementationVisitor::LowerParameter( |
| const Type* type, const std::string& parameter_name, |
| Stack<std::string>* lowered_parameters) { |
| if (base::Optional<const StructType*> struct_type = type->StructSupertype()) { |
| StackRange range = lowered_parameters->TopRange(0); |
| for (auto& field : (*struct_type)->fields()) { |
| StackRange parameter_range = LowerParameter( |
| field.name_and_type.type, |
| parameter_name + "." + field.name_and_type.name, lowered_parameters); |
| range.Extend(parameter_range); |
| } |
| return range; |
| } else { |
| lowered_parameters->Push(parameter_name); |
| return lowered_parameters->TopRange(1); |
| } |
| } |
| |
| void ImplementationVisitor::LowerLabelParameter( |
| const Type* type, const std::string& parameter_name, |
| std::vector<std::string>* lowered_parameters) { |
| if (base::Optional<const StructType*> struct_type = type->StructSupertype()) { |
| for (auto& field : (*struct_type)->fields()) { |
| LowerLabelParameter( |
| field.name_and_type.type, |
| "&((*" + parameter_name + ")." + field.name_and_type.name + ")", |
| lowered_parameters); |
| } |
| } else { |
| lowered_parameters->push_back(parameter_name); |
| } |
| } |
| |
| std::string ImplementationVisitor::ExternalLabelName( |
| const std::string& label_name) { |
| return "label_" + label_name; |
| } |
| |
| std::string ImplementationVisitor::ExternalLabelParameterName( |
| const std::string& label_name, size_t i) { |
| return "label_" + label_name + "_parameter_" + std::to_string(i); |
| } |
| |
| std::string ImplementationVisitor::ExternalParameterName( |
| const std::string& name) { |
| return std::string("p_") + name; |
| } |
| |
| DEFINE_CONTEXTUAL_VARIABLE(ImplementationVisitor::ValueBindingsManager) |
| DEFINE_CONTEXTUAL_VARIABLE(ImplementationVisitor::LabelBindingsManager) |
| DEFINE_CONTEXTUAL_VARIABLE(ImplementationVisitor::CurrentCallable) |
| DEFINE_CONTEXTUAL_VARIABLE(ImplementationVisitor::CurrentFileStreams) |
| DEFINE_CONTEXTUAL_VARIABLE(ImplementationVisitor::CurrentReturnValue) |
| |
| bool IsCompatibleSignature(const Signature& sig, const TypeVector& types, |
| size_t label_count) { |
| auto i = sig.parameter_types.types.begin() + sig.implicit_count; |
| if ((sig.parameter_types.types.size() - sig.implicit_count) > types.size()) |
| return false; |
| if (sig.labels.size() != label_count) return false; |
| for (auto current : types) { |
| if (i == sig.parameter_types.types.end()) { |
| if (!sig.parameter_types.var_args) return false; |
| if (!IsAssignableFrom(TypeOracle::GetObjectType(), current)) return false; |
| } else { |
| if (!IsAssignableFrom(*i++, current)) return false; |
| } |
| } |
| return true; |
| } |
| |
| base::Optional<Block*> ImplementationVisitor::GetCatchBlock() { |
| base::Optional<Block*> catch_block; |
| if (base::Optional<Binding<LocalLabel>*> catch_handler = |
| TryLookupLabel(kCatchLabelName)) { |
| catch_block = assembler().NewBlock(base::nullopt, true); |
| } |
| return catch_block; |
| } |
| |
| void ImplementationVisitor::GenerateCatchBlock( |
| base::Optional<Block*> catch_block) { |
| if (catch_block) { |
| base::Optional<Binding<LocalLabel>*> catch_handler = |
| TryLookupLabel(kCatchLabelName); |
| if (assembler().CurrentBlockIsComplete()) { |
| assembler().Bind(*catch_block); |
| assembler().Goto((*catch_handler)->block, 1); |
| } else { |
| CfgAssemblerScopedTemporaryBlock temp(&assembler(), *catch_block); |
| assembler().Goto((*catch_handler)->block, 1); |
| } |
| } |
| } |
| void ImplementationVisitor::VisitAllDeclarables() { |
| CurrentCallable::Scope current_callable(nullptr); |
| const std::vector<std::unique_ptr<Declarable>>& all_declarables = |
| GlobalContext::AllDeclarables(); |
| |
| // This has to be an index-based loop because all_declarables can be extended |
| // during the loop. |
| for (size_t i = 0; i < all_declarables.size(); ++i) { |
| try { |
| Visit(all_declarables[i].get()); |
| } catch (TorqueAbortCompilation&) { |
| // Recover from compile errors here. The error is recorded already. |
| } |
| } |
| |
| // Do the same for macros which generate C++ code. |
| output_type_ = OutputType::kCC; |
| const std::vector<TorqueMacro*>& cc_macros = |
| GlobalContext::AllMacrosForCCOutput(); |
| for (size_t i = 0; i < cc_macros.size(); ++i) { |
| try { |
| Visit(static_cast<Declarable*>(cc_macros[i])); |
| } catch (TorqueAbortCompilation&) { |
| // Recover from compile errors here. The error is recorded already. |
| } |
| } |
| output_type_ = OutputType::kCSA; |
| } |
| |
| void ImplementationVisitor::Visit(Declarable* declarable) { |
| CurrentScope::Scope current_scope(declarable->ParentScope()); |
| CurrentSourcePosition::Scope current_source_position(declarable->Position()); |
| CurrentFileStreams::Scope current_file_streams( |
| &GlobalContext::GeneratedPerFile(declarable->Position().source)); |
| if (Callable* callable = Callable::DynamicCast(declarable)) { |
| if (!callable->ShouldGenerateExternalCode(output_type_)) |
| CurrentFileStreams::Get() = nullptr; |
| } |
| switch (declarable->kind()) { |
| case Declarable::kExternMacro: |
| return Visit(ExternMacro::cast(declarable)); |
| case Declarable::kTorqueMacro: |
| return Visit(TorqueMacro::cast(declarable)); |
| case Declarable::kMethod: |
| return Visit(Method::cast(declarable)); |
| case Declarable::kBuiltin: |
| return Visit(Builtin::cast(declarable)); |
| case Declarable::kTypeAlias: |
| return Visit(TypeAlias::cast(declarable)); |
| case Declarable::kNamespaceConstant: |
| return Visit(NamespaceConstant::cast(declarable)); |
| case Declarable::kRuntimeFunction: |
| case Declarable::kIntrinsic: |
| case Declarable::kExternConstant: |
| case Declarable::kNamespace: |
| case Declarable::kGenericCallable: |
| case Declarable::kGenericType: |
| return; |
| } |
| } |
| |
| std::string MachineTypeString(const Type* type) { |
| if (type->IsSubtypeOf(TypeOracle::GetSmiType())) { |
| return "MachineType::TaggedSigned()"; |
| } |
| if (type->IsSubtypeOf(TypeOracle::GetHeapObjectType())) { |
| return "MachineType::TaggedPointer()"; |
| } |
| if (type->IsSubtypeOf(TypeOracle::GetTaggedType())) { |
| return "MachineType::AnyTagged()"; |
| } |
| return "MachineTypeOf<" + type->GetGeneratedTNodeTypeName() + ">::value"; |
| } |
| |
| void ImplementationVisitor::GenerateBuiltinDefinitionsAndInterfaceDescriptors( |
| const std::string& output_directory) { |
| std::stringstream builtin_definitions; |
| std::string builtin_definitions_file_name = "builtin-definitions.h"; |
| |
| // This file contains plain interface descriptor definitions and has to be |
| // included in the middle of interface-descriptors.h. Thus it is not a normal |
| // header file and uses the .inc suffix instead of the .h suffix. |
| std::stringstream interface_descriptors; |
| std::string interface_descriptors_file_name = "interface-descriptors.inc"; |
| { |
| IncludeGuardScope builtin_definitions_include_guard( |
| builtin_definitions, builtin_definitions_file_name); |
| |
| builtin_definitions |
| << "\n" |
| "#define BUILTIN_LIST_FROM_TORQUE(CPP, TFJ, TFC, TFS, TFH, " |
| "ASM) " |
| "\\\n"; |
| for (auto& declarable : GlobalContext::AllDeclarables()) { |
| Builtin* builtin = Builtin::DynamicCast(declarable.get()); |
| if (!builtin || builtin->IsExternal()) continue; |
| if (builtin->IsStub()) { |
| builtin_definitions << "TFC(" << builtin->ExternalName() << ", " |
| << builtin->ExternalName(); |
| std::string descriptor_name = builtin->ExternalName() + "Descriptor"; |
| bool has_context_parameter = builtin->signature().HasContextParameter(); |
| size_t kFirstNonContextParameter = has_context_parameter ? 1 : 0; |
| size_t parameter_count = |
| builtin->parameter_names().size() - kFirstNonContextParameter; |
| |
| interface_descriptors |
| << "class " << descriptor_name |
| << " : public TorqueInterfaceDescriptor<" << parameter_count << ", " |
| << (has_context_parameter ? "true" : "false") << "> {\n"; |
| interface_descriptors << " DECLARE_DESCRIPTOR_WITH_BASE(" |
| << descriptor_name |
| << ", TorqueInterfaceDescriptor)\n"; |
| |
| interface_descriptors << " MachineType ReturnType() override {\n"; |
| interface_descriptors |
| << " return " |
| << MachineTypeString(builtin->signature().return_type) << ";\n"; |
| interface_descriptors << " }\n"; |
| |
| interface_descriptors << " std::array<MachineType, " << parameter_count |
| << "> ParameterTypes() override {\n"; |
| interface_descriptors << " return {"; |
| for (size_t i = kFirstNonContextParameter; |
| i < builtin->parameter_names().size(); ++i) { |
| bool last = i + 1 == builtin->parameter_names().size(); |
| const Type* type = builtin->signature().parameter_types.types[i]; |
| interface_descriptors << MachineTypeString(type) |
| << (last ? "" : ", "); |
| } |
| interface_descriptors << "};\n"; |
| |
| interface_descriptors << " }\n"; |
| interface_descriptors << "};\n\n"; |
| } else { |
| builtin_definitions << "TFJ(" << builtin->ExternalName(); |
| if (builtin->IsVarArgsJavaScript()) { |
| builtin_definitions << ", kDontAdaptArgumentsSentinel"; |
| } else { |
| DCHECK(builtin->IsFixedArgsJavaScript()); |
| // FixedArg javascript builtins need to offer the parameter |
| // count. |
| int parameter_count = |
| static_cast<int>(builtin->signature().ExplicitCount()); |
| builtin_definitions << ", " << parameter_count; |
| // And the receiver is explicitly declared. |
| builtin_definitions << ", kReceiver"; |
| for (size_t i = builtin->signature().implicit_count; |
| i < builtin->parameter_names().size(); ++i) { |
| Identifier* parameter = builtin->parameter_names()[i]; |
| builtin_definitions << ", k" << CamelifyString(parameter->value); |
| } |
| } |
| } |
| builtin_definitions << ") \\\n"; |
| } |
| builtin_definitions << "\n"; |
| |
| builtin_definitions |
| << "#define TORQUE_FUNCTION_POINTER_TYPE_TO_BUILTIN_MAP(V) \\\n"; |
| for (const BuiltinPointerType* type : |
| TypeOracle::AllBuiltinPointerTypes()) { |
| Builtin* example_builtin = |
| Declarations::FindSomeInternalBuiltinWithType(type); |
| if (!example_builtin) { |
| CurrentSourcePosition::Scope current_source_position( |
| SourcePosition{CurrentSourceFile::Get(), {-1, -1}, {-1, -1}}); |
| ReportError("unable to find any builtin with type \"", *type, "\""); |
| } |
| builtin_definitions << " V(" << type->function_pointer_type_id() << "," |
| << example_builtin->ExternalName() << ")\\\n"; |
| } |
| builtin_definitions << "\n"; |
| } |
| WriteFile(output_directory + "/" + builtin_definitions_file_name, |
| builtin_definitions.str()); |
| WriteFile(output_directory + "/" + interface_descriptors_file_name, |
| interface_descriptors.str()); |
| } |
| |
| namespace { |
| |
| enum class FieldSectionType : uint32_t { |
| kNoSection = 0, |
| kWeakSection = 1 << 0, |
| kStrongSection = 2 << 0, |
| kScalarSection = 3 << 0 |
| }; |
| |
| bool IsPointerSection(FieldSectionType type) { |
| return type == FieldSectionType::kWeakSection || |
| type == FieldSectionType::kStrongSection; |
| } |
| |
| using FieldSections = base::Flags<FieldSectionType>; |
| |
| std::string ToString(FieldSectionType type) { |
| switch (type) { |
| case FieldSectionType::kNoSection: |
| return "NoSection"; |
| break; |
| case FieldSectionType::kWeakSection: |
| return "WeakFields"; |
| break; |
| case FieldSectionType::kStrongSection: |
| return "StrongFields"; |
| break; |
| case FieldSectionType::kScalarSection: |
| return "ScalarFields"; |
| break; |
| } |
| UNREACHABLE(); |
| } |
| |
| class FieldOffsetsGenerator { |
| public: |
| explicit FieldOffsetsGenerator(const ClassType* type) : type_(type) {} |
| |
| virtual void WriteField(const Field& f, const std::string& size_string) = 0; |
| virtual void WriteMarker(const std::string& marker) = 0; |
| |
| virtual ~FieldOffsetsGenerator() { CHECK(is_finished_); } |
| |
| void RecordOffsetFor(const Field& f) { |
| CHECK(!is_finished_); |
| UpdateSection(f); |
| |
| // Emit kHeaderSize before any indexed field. |
| if (f.index.has_value() && !header_size_emitted_) { |
| WriteMarker("kHeaderSize"); |
| header_size_emitted_ = true; |
| } |
| |
| // We don't know statically how much space an indexed field takes, so report |
| // it as zero. |
| std::string size_string = "0"; |
| if (!f.index.has_value()) { |
| size_t field_size; |
| std::tie(field_size, size_string) = f.GetFieldSizeInformation(); |
| } |
| WriteField(f, size_string); |
| } |
| |
| void Finish() { |
| End(current_section_); |
| if (!(completed_sections_ & FieldSectionType::kWeakSection)) { |
| Begin(FieldSectionType::kWeakSection); |
| End(FieldSectionType::kWeakSection); |
| } |
| if (!(completed_sections_ & FieldSectionType::kStrongSection)) { |
| Begin(FieldSectionType::kStrongSection); |
| End(FieldSectionType::kStrongSection); |
| } |
| is_finished_ = true; |
| |
| // In the presence of indexed fields, we already emitted kHeaderSize before |
| // the indexed field. |
| if (!type_->IsShape() && !header_size_emitted_) { |
| WriteMarker("kHeaderSize"); |
| } |
| if (!type_->IsAbstract() && type_->HasStaticSize()) { |
| WriteMarker("kSize"); |
| } |
| } |
| |
| protected: |
| const ClassType* type_; |
| |
| private: |
| FieldSectionType GetSectionFor(const Field& f) { |
| const Type* field_type = f.name_and_type.type; |
| if (field_type == TypeOracle::GetVoidType()) { |
| // Allow void type for marker constants of size zero. |
| return current_section_; |
| } |
| StructType::Classification struct_contents = |
| StructType::ClassificationFlag::kEmpty; |
| if (auto field_as_struct = field_type->StructSupertype()) { |
| struct_contents = (*field_as_struct)->ClassifyContents(); |
| } |
| if (struct_contents == StructType::ClassificationFlag::kMixed) { |
| // We can't declare what section a struct goes in if it has multiple |
| // categories of data within. |
| Error( |
| "Classes do not support fields which are structs containing both " |
| "tagged and untagged data.") |
| .Position(f.pos); |
| } |
| // Currently struct-valued fields are only allowed to have tagged data; see |
| // TypeVisitor::VisitClassFieldsAndMethods. |
| if (field_type->IsSubtypeOf(TypeOracle::GetTaggedType()) || |
| struct_contents == StructType::ClassificationFlag::kTagged) { |
| if (f.is_weak) { |
| return FieldSectionType::kWeakSection; |
| } else { |
| return FieldSectionType::kStrongSection; |
| } |
| } else { |
| return FieldSectionType::kScalarSection; |
| } |
| } |
| void UpdateSection(const Field& f) { |
| FieldSectionType type = GetSectionFor(f); |
| if (current_section_ == type) return; |
| if (IsPointerSection(type)) { |
| if (completed_sections_ & type) { |
| std::stringstream s; |
| s << "cannot declare field " << f.name_and_type.name << " in class " |
| << type_->name() << ", because section " << ToString(type) |
| << " to which it belongs has already been finished."; |
| Error(s.str()).Position(f.pos); |
| } |
| } |
| End(current_section_); |
| current_section_ = type; |
| Begin(current_section_); |
| } |
| void Begin(FieldSectionType type) { |
| DCHECK(type != FieldSectionType::kNoSection); |
| if (!IsPointerSection(type)) return; |
| WriteMarker("kStartOf" + ToString(type) + "Offset"); |
| } |
| void End(FieldSectionType type) { |
| if (!IsPointerSection(type)) return; |
| completed_sections_ |= type; |
| WriteMarker("kEndOf" + ToString(type) + "Offset"); |
| } |
| |
| FieldSectionType current_section_ = FieldSectionType::kNoSection; |
| FieldSections completed_sections_ = FieldSectionType::kNoSection; |
| bool is_finished_ = false; |
| bool header_size_emitted_ = false; |
| }; |
| |
| class MacroFieldOffsetsGenerator : public FieldOffsetsGenerator { |
| public: |
| MacroFieldOffsetsGenerator(std::ostream& out, const ClassType* type) |
| : FieldOffsetsGenerator(type), out_(out) { |
| out_ << "#define "; |
| out_ << "TORQUE_GENERATED_" << CapifyStringWithUnderscores(type_->name()) |
| << "_FIELDS(V) \\\n"; |
| } |
| void WriteField(const Field& f, const std::string& size_string) override { |
| out_ << "V(k" << CamelifyString(f.name_and_type.name) << "Offset, " |
| << size_string << ") \\\n"; |
| } |
| void WriteMarker(const std::string& marker) override { |
| out_ << "V(" << marker << ", 0) \\\n"; |
| } |
| |
| private: |
| std::ostream& out_; |
| }; |
| |
| void GenerateClassExport(const ClassType* type, std::ostream& header, |
| std::ostream& inl_header) { |
| const ClassType* super = type->GetSuperClass(); |
| std::string parent = "TorqueGenerated" + type->name() + "<" + type->name() + |
| ", " + super->name() + ">"; |
| header << "class " << type->name() << " : public " << parent << " {\n"; |
| header << " public:\n"; |
| if (type->ShouldGenerateBodyDescriptor()) { |
| header << " class BodyDescriptor;\n"; |
| } |
| header << " TQ_OBJECT_CONSTRUCTORS(" << type->name() << ")\n"; |
| header << "};\n\n"; |
| inl_header << "TQ_OBJECT_CONSTRUCTORS_IMPL(" << type->name() << ")\n"; |
| } |
| |
| } // namespace |
| |
| void ImplementationVisitor::GenerateClassFieldOffsets( |
| const std::string& output_directory) { |
| std::stringstream header; |
| std::string file_name = "field-offsets.h"; |
| { |
| IncludeGuardScope include_guard(header, file_name); |
| |
| for (const ClassType* type : TypeOracle::GetClasses()) { |
| // TODO(danno): Remove this once all classes use ClassFieldOffsetGenerator |
| // to generate field offsets without the use of macros. |
| if (!type->GenerateCppClassDefinitions() && !type->HasUndefinedLayout()) { |
| MacroFieldOffsetsGenerator g(header, type); |
| for (auto f : type->fields()) { |
| CurrentSourcePosition::Scope scope(f.pos); |
| g.RecordOffsetFor(f); |
| } |
| g.Finish(); |
| header << "\n"; |
| } |
| } |
| |
| header << "#define TORQUE_INSTANCE_TYPE_TO_BODY_DESCRIPTOR_LIST(V)\\\n"; |
| for (const ClassType* type : TypeOracle::GetClasses()) { |
| if (type->ShouldGenerateBodyDescriptor() && type->OwnInstanceType()) { |
| std::string type_name = |
| CapifyStringWithUnderscores(type->name()) + "_TYPE"; |
| header << "V(" << type_name << "," << type->name() << ")\\\n"; |
| } |
| } |
| header << "\n"; |
| |
| header << "#define TORQUE_DATA_ONLY_VISITOR_ID_LIST(V)\\\n"; |
| for (const ClassType* type : TypeOracle::GetClasses()) { |
| if (type->ShouldGenerateBodyDescriptor() && type->HasNoPointerSlots()) { |
| header << "V(" << type->name() << ")\\\n"; |
| } |
| } |
| header << "\n"; |
| |
| header << "#define TORQUE_POINTER_VISITOR_ID_LIST(V)\\\n"; |
| for (const ClassType* type : TypeOracle::GetClasses()) { |
| if (type->ShouldGenerateBodyDescriptor() && !type->HasNoPointerSlots()) { |
| header << "V(" << type->name() << ")\\\n"; |
| } |
| } |
| header << "\n"; |
| } |
| const std::string output_header_path = output_directory + "/" + file_name; |
| WriteFile(output_header_path, header.str()); |
| } |
| |
| void ImplementationVisitor::GenerateBitFields( |
| const std::string& output_directory) { |
| std::stringstream header; |
| std::string file_name = "bit-fields.h"; |
| { |
| IncludeGuardScope include_guard(header, file_name); |
| header << "#include \"src/base/bit-field.h\"\n\n"; |
| NamespaceScope namespaces(header, {"v8", "internal"}); |
| |
| for (const auto& type : TypeOracle::GetBitFieldStructTypes()) { |
| bool all_single_bits = true; // Track whether every field is one bit. |
| |
| header << "#define DEFINE_TORQUE_GENERATED_" |
| << CapifyStringWithUnderscores(type->name()) << "() \\\n"; |
| std::string type_name = type->GetConstexprGeneratedTypeName(); |
| for (const auto& field : type->fields()) { |
| const char* suffix = field.num_bits == 1 ? "Bit" : "Bits"; |
| all_single_bits = all_single_bits && field.num_bits == 1; |
| std::string field_type_name = |
| field.name_and_type.type->GetConstexprGeneratedTypeName(); |
| header << " using " << CamelifyString(field.name_and_type.name) |
| << suffix << " = base::BitField<" << field_type_name << ", " |
| << field.offset << ", " << field.num_bits << ", " << type_name |
| << ">; \\\n"; |
| } |
| |
| // If every field is one bit, we can also generate a convenient enum. |
| if (all_single_bits) { |
| header << " enum Flag { \\\n"; |
| header << " kNone = 0, \\\n"; |
| for (const auto& field : type->fields()) { |
| header << " k" << CamelifyString(field.name_and_type.name) |
| << " = 1 << " << field.offset << ", \\\n"; |
| } |
| header << " }; \\\n"; |
| header << " using Flags = base::Flags<Flag>; \\\n"; |
| header << " static constexpr int kFlagCount = " |
| << type->fields().size() << "; \\\n"; |
| } |
| |
| header << "\n"; |
| } |
| } |
| const std::string output_header_path = output_directory + "/" + file_name; |
| WriteFile(output_header_path, header.str()); |
| } |
| |
| namespace { |
| |
| class ClassFieldOffsetGenerator : public FieldOffsetsGenerator { |
| public: |
| ClassFieldOffsetGenerator(std::ostream& header, const ClassType* type) |
| : FieldOffsetsGenerator(type), |
| hdr_(header), |
| previous_field_end_("P::kHeaderSize") {} |
| void WriteField(const Field& f, const std::string& size_string) override { |
| std::string field = "k" + CamelifyString(f.name_and_type.name) + "Offset"; |
| std::string field_end = field + "End"; |
| hdr_ << " static constexpr int " << field << " = " << previous_field_end_ |
| << ";\n"; |
| hdr_ << " static constexpr int " << field_end << " = " << field << " + " |
| << size_string << " - 1;\n"; |
| previous_field_end_ = field_end + " + 1"; |
| } |
| void WriteMarker(const std::string& marker) override { |
| hdr_ << " static constexpr int " << marker << " = " << previous_field_end_ |
| << ";\n"; |
| } |
| |
| private: |
| std::ostream& hdr_; |
| std::string previous_field_end_; |
| }; |
| |
| class CppClassGenerator { |
| public: |
| CppClassGenerator(const ClassType* type, std::ostream& header, |
| std::ostream& inl_header, std::ostream& impl) |
| : type_(type), |
| super_(type->GetSuperClass()), |
| name_(type->name()), |
| gen_name_("TorqueGenerated" + name_), |
| gen_name_T_(gen_name_ + "<D, P>"), |
| gen_name_I_(gen_name_ + "<" + name_ + ", " + super_->name() + ">"), |
| hdr_(header), |
| inl_(inl_header), |
| impl_(impl) {} |
| const std::string template_decl() const { |
| return "template <class D, class P>"; |
| } |
| |
| void GenerateClass(); |
| |
| private: |
| void GenerateClassConstructors(); |
| void GenerateFieldAccessor(const Field& f); |
| void GenerateFieldAccessorForUntagged(const Field& f); |
| void GenerateFieldAccessorForSmi(const Field& f); |
| void GenerateFieldAccessorForTagged(const Field& f); |
| |
| void GenerateClassCasts(); |
| |
| const ClassType* type_; |
| const ClassType* super_; |
| const std::string name_; |
| const std::string gen_name_; |
| const std::string gen_name_T_; |
| const std::string gen_name_I_; |
| std::ostream& hdr_; |
| std::ostream& inl_; |
| std::ostream& impl_; |
| }; |
| |
| base::Optional<std::vector<Field>> GetOrderedUniqueIndexFields( |
| const ClassType& type) { |
| std::vector<Field> result; |
| std::set<std::string> index_names; |
| for (const Field& field : type.ComputeAllFields()) { |
| if (field.index) { |
| auto name_and_type = ExtractSimpleFieldArraySize(type, *field.index); |
| if (!name_and_type) { |
| return base::nullopt; |
| } |
| index_names.insert(name_and_type->name); |
| } |
| } |
| |
| for (const Field& field : type.ComputeAllFields()) { |
| if (index_names.count(field.name_and_type.name) != 0) { |
| result.push_back(field); |
| } |
| } |
| |
| return result; |
| } |
| |
| void CppClassGenerator::GenerateClass() { |
| hdr_ << "\n"; |
| hdr_ << "// Alias for HeapObject::Is" << name_ |
| << "() that avoids inlining.\n"; |
| hdr_ << "V8_EXPORT_PRIVATE bool Is" << name_ << "_NonInline(HeapObject o);\n"; |
| hdr_ << "\n"; |
| |
| impl_ << "\n"; |
| impl_ << "bool Is" << name_ << "_NonInline(HeapObject o) {\n"; |
| impl_ << " return o.Is" << name_ << "();\n"; |
| impl_ << "}\n\n"; |
| |
| hdr_ << template_decl() << "\n"; |
| hdr_ << "class " << gen_name_ << " : public P {\n"; |
| hdr_ << " static_assert(std::is_same<" << name_ << ", D>::value,\n" |
| << " \"Use this class as direct base for " << name_ << ".\");\n"; |
| hdr_ << " static_assert(std::is_same<" << super_->name() << ", P>::value,\n" |
| << " \"Pass in " << super_->name() |
| << " as second template parameter for " << gen_name_ << ".\");\n"; |
| hdr_ << " public: \n"; |
| hdr_ << " using Super = P;\n\n"; |
| if (!type_->ShouldExport() && !type_->IsExtern()) { |
| hdr_ << " protected: // not extern or @export\n"; |
| } |
| for (const Field& f : type_->fields()) { |
| GenerateFieldAccessor(f); |
| } |
| if (!type_->ShouldExport() && !type_->IsExtern()) { |
| hdr_ << " public:\n"; |
| } |
| |
| GenerateClassCasts(); |
| |
| if (type_->ShouldGeneratePrint()) { |
| hdr_ << "\n DECL_PRINTER(" << name_ << ")\n"; |
| } |
| |
| if (type_->ShouldGenerateVerify()) { |
| IfDefScope hdr_scope(hdr_, "VERIFY_HEAP"); |
| hdr_ << " V8_EXPORT_PRIVATE void " << name_ |
| << "Verify(Isolate* isolate);\n"; |
| |
| IfDefScope impl_scope(impl_, "VERIFY_HEAP"); |
| impl_ << "\ntemplate <>\n"; |
| impl_ << "void " << gen_name_I_ << "::" << name_ |
| << "Verify(Isolate* isolate) {\n"; |
| impl_ << " TorqueGeneratedClassVerifiers::" << name_ << "Verify(" << name_ |
| << "::cast(*this), " |
| "isolate);\n"; |
| impl_ << "}\n"; |
| } |
| |
| hdr_ << "\n"; |
| ClassFieldOffsetGenerator g(hdr_, type_); |
| for (auto f : type_->fields()) { |
| CurrentSourcePosition::Scope scope(f.pos); |
| g.RecordOffsetFor(f); |
| } |
| g.Finish(); |
| hdr_ << "\n"; |
| |
| auto index_fields = GetOrderedUniqueIndexFields(*type_); |
| |
| if (!index_fields.has_value()) { |
| hdr_ << " // SizeFor implementations not generated due to complex array " |
| "lengths\n\n"; |
| } else if (!type_->IsAbstract() && |
| !type_->IsSubtypeOf(TypeOracle::GetJSObjectType())) { |
| hdr_ << " V8_INLINE static constexpr int32_t SizeFor("; |
| bool first = true; |
| for (const Field& field : *index_fields) { |
| if (!first) hdr_ << ", "; |
| hdr_ << "int " << field.name_and_type.name; |
| first = false; |
| } |
| hdr_ << ") {\n"; |
| if (index_fields->empty()) { |
| hdr_ << " DCHECK(kHeaderSize == kSize && kHeaderSize == " |
| << *type_->size().SingleValue() << ");\n"; |
| } |
| hdr_ << " int32_t size = kHeaderSize;\n"; |
| for (const Field& field : type_->ComputeAllFields()) { |
| if (field.index) { |
| auto index_name_and_type = |
| *ExtractSimpleFieldArraySize(*type_, *field.index); |
| size_t field_size = 0; |
| std::tie(field_size, std::ignore) = field.GetFieldSizeInformation(); |
| hdr_ << " size += " << index_name_and_type.name << " * " |
| << field_size << ";\n"; |
| } |
| if (type_->size().Alignment() < TargetArchitecture::TaggedSize()) { |
| hdr_ << " size = OBJECT_POINTER_ALIGN(size);\n"; |
| } |
| } |
| hdr_ << " return size;\n"; |
| hdr_ << " }\n\n"; |
| hdr_ << " V8_INLINE int32_t AllocatedSize() {\n"; |
| hdr_ << " return SizeFor("; |
| first = true; |
| for (auto field : *index_fields) { |
| if (!first) hdr_ << ", "; |
| hdr_ << "this->" << field.name_and_type.name << "()"; |
| first = false; |
| } |
| hdr_ << ");\n }\n"; |
| hdr_ << "\n"; |
| } |
| |
| hdr_ << " friend class Factory;\n\n"; |
| |
| GenerateClassConstructors(); |
| |
| hdr_ << "};\n\n"; |
| |
| if (type_->ShouldGenerateFullClassDefinition()) { |
| GenerateClassExport(type_, hdr_, inl_); |
| } |
| } |
| |
| void CppClassGenerator::GenerateClassCasts() { |
| hdr_ << " V8_INLINE static D cast(Object object) {\n"; |
| hdr_ << " return D(object.ptr());\n"; |
| hdr_ << " }\n"; |
| |
| hdr_ << " V8_INLINE static D unchecked_cast(Object object) {\n"; |
| hdr_ << " return bit_cast<D>(object);\n"; |
| hdr_ << " }\n"; |
| } |
| |
| void CppClassGenerator::GenerateClassConstructors() { |
| hdr_ << " public:\n"; |
| hdr_ << " template <class DAlias = D>\n"; |
| hdr_ << " constexpr " << gen_name_ << "() : P() {\n"; |
| hdr_ << " static_assert(std::is_base_of<" << gen_name_ << ", \n"; |
| hdr_ << " DAlias>::value,\n"; |
| hdr_ << " \"class " << gen_name_ << " should be used as direct base for " |
| << name_ << ".\");\n"; |
| hdr_ << " }\n"; |
| |
| hdr_ << " protected:\n"; |
| hdr_ << " inline explicit " << gen_name_ << "(Address ptr);\n"; |
| hdr_ << " // Special-purpose constructor for subclasses that have fast " |
| "paths where\n"; |
| hdr_ << " // their ptr() is a Smi.\n"; |
| hdr_ << " inline explicit " << gen_name_ |
| << "(Address ptr, HeapObject::AllowInlineSmiStorage allow_smi);\n"; |
| |
| inl_ << "template<class D, class P>\n"; |
| inl_ << "inline " << gen_name_T_ << "::" << gen_name_ << "(Address ptr)\n"; |
| inl_ << " : P(ptr) {\n"; |
| inl_ << " SLOW_DCHECK(Is" << name_ << "_NonInline(*this));\n"; |
| inl_ << "}\n"; |
| |
| inl_ << "template<class D, class P>\n"; |
| inl_ << "inline " << gen_name_T_ << "::" << gen_name_ |
| << "(Address ptr, HeapObject::AllowInlineSmiStorage allow_smi)\n"; |
| inl_ << " : P(ptr, allow_smi) {\n"; |
| inl_ << " SLOW_DCHECK(" |
| << "(allow_smi == HeapObject::AllowInlineSmiStorage::kAllowBeingASmi" |
| " && this->IsSmi()) || Is" |
| << name_ << "_NonInline(*this));\n"; |
| inl_ << "}\n"; |
| } |
| |
| namespace { |
| std::string GenerateRuntimeTypeCheck(const Type* type, |
| const std::string& value) { |
| bool maybe_object = !type->IsSubtypeOf(TypeOracle::GetStrongTaggedType()); |
| std::stringstream type_check; |
| bool at_start = true; |
| // If weak pointers are allowed, then start by checking for a cleared value. |
| if (maybe_object) { |
| type_check << value << ".IsCleared()"; |
| at_start = false; |
| } |
| for (const TypeChecker& runtime_type : type->GetTypeCheckers()) { |
| if (!at_start) type_check << " || "; |
| at_start = false; |
| if (maybe_object) { |
| bool strong = runtime_type.weak_ref_to.empty(); |
| if (strong && runtime_type.type == WEAK_HEAP_OBJECT) { |
| // Rather than a generic Weak<T>, this is the basic type WeakHeapObject. |
| // We can't validate anything more about the type of the object pointed |
| // to, so just check that it's weak. |
| type_check << value << ".IsWeak()"; |
| } else { |
| type_check << "(" << (strong ? "!" : "") << value << ".IsWeak() && " |
| << value << ".GetHeapObjectOrSmi().Is" |
| << (strong ? runtime_type.type : runtime_type.weak_ref_to) |
| << "())"; |
| } |
| } else { |
| type_check << value << ".Is" << runtime_type.type << "()"; |
| } |
| } |
| return type_check.str(); |
| } |
| |
| void GenerateBoundsDCheck(std::ostream& os, const std::string& index, |
| const ClassType* type, const Field& f) { |
| os << " DCHECK_GE(" << index << ", 0);\n"; |
| if (base::Optional<NameAndType> array_length = |
| ExtractSimpleFieldArraySize(*type, *f.index)) { |
| os << " DCHECK_LT(" << index << ", this->" << array_length->name |
| << "());\n"; |
| } |
| } |
| } // namespace |
| |
| // TODO(sigurds): Keep in sync with DECL_ACCESSORS and ACCESSORS macro. |
| void CppClassGenerator::GenerateFieldAccessor(const Field& f) { |
| const Type* field_type = f.name_and_type.type; |
| if (field_type == TypeOracle::GetVoidType()) return; |
| |
| // TODO(danno): Support generation of struct accessors |
| if (f.name_and_type.type->IsStructType()) return; |
| |
| // TODO(v8:10391) Generate accessors for external pointers |
| if (f.name_and_type.type->IsSubtypeOf(TypeOracle::GetExternalPointerType())) { |
| return; |
| } |
| |
| if (!f.name_and_type.type->IsSubtypeOf(TypeOracle::GetTaggedType())) { |
| return GenerateFieldAccessorForUntagged(f); |
| } |
| if (f.name_and_type.type->IsSubtypeOf(TypeOracle::GetSmiType())) { |
| return GenerateFieldAccessorForSmi(f); |
| } |
| if (f.name_and_type.type->IsSubtypeOf(TypeOracle::GetTaggedType())) { |
| return GenerateFieldAccessorForTagged(f); |
| } |
| |
| Error("Generation of field accessor for ", type_->name(), |
| "::", f.name_and_type.name, " failed (type ", *field_type, |
| " is not supported).") |
| .Position(f.pos); |
| } |
| |
| void CppClassGenerator::GenerateFieldAccessorForUntagged(const Field& f) { |
| DCHECK(!f.name_and_type.type->IsSubtypeOf(TypeOracle::GetTaggedType())); |
| const Type* field_type = f.name_and_type.type; |
| if (field_type == TypeOracle::GetVoidType()) return; |
| const Type* constexpr_version = field_type->ConstexprVersion(); |
| if (!constexpr_version) { |
| Error("Field accessor for ", type_->name(), ":: ", f.name_and_type.name, |
| " cannot be generated because its type ", *field_type, |
| " is neither a subclass of Object nor does the type have a constexpr " |
| "version.") |
| .Position(f.pos); |
| return; |
| } |
| const std::string& name = f.name_and_type.name; |
| const std::string type = constexpr_version->GetGeneratedTypeName(); |
| std::string offset = "k" + CamelifyString(name) + "Offset"; |
| |
| // Generate declarations in header. |
| if (f.index) { |
| hdr_ << " inline " << type << " " << name << "(int i) const;\n"; |
| hdr_ << " inline void set_" << name << "(int i, " << type |
| << " value);\n\n"; |
| } else { |
| hdr_ << " inline " << type << " " << name << "() const;\n"; |
| hdr_ << " inline void set_" << name << "(" << type << " value);\n\n"; |
| } |
| |
| // Generate implementation in inline header. |
| inl_ << "template <class D, class P>\n"; |
| inl_ << type << " " << gen_name_ << "<D, P>::" << name << "("; |
| if (f.index) { |
| inl_ << "int i"; |
| } |
| inl_ << ") const {\n"; |
| if (f.index) { |
| GenerateBoundsDCheck(inl_, "i", type_, f); |
| size_t field_size; |
| std::string size_string; |
| std::tie(field_size, size_string) = f.GetFieldSizeInformation(); |
| inl_ << " int offset = " << offset << " + i * " << field_size << ";\n"; |
| inl_ << " return this->template ReadField<" << type << ">(offset);\n"; |
| } else { |
| inl_ << " return this->template ReadField<" << type << ">(" << offset |
| << ");\n"; |
| } |
| inl_ << "}\n"; |
| |
| inl_ << "template <class D, class P>\n"; |
| inl_ << "void " << gen_name_ << "<D, P>::set_" << name << "("; |
| if (f.index) { |
| inl_ << "int i, "; |
| } |
| inl_ << type << " value) {\n"; |
| if (f.index) { |
| GenerateBoundsDCheck(inl_, "i", type_, f); |
| size_t field_size; |
| std::string size_string; |
| std::tie(field_size, size_string) = f.GetFieldSizeInformation(); |
| inl_ << " int offset = " << offset << " + i * " << field_size << ";\n"; |
| inl_ << " this->template WriteField<" << type << ">(offset, value);\n"; |
| } else { |
| inl_ << " this->template WriteField<" << type << ">(" << offset |
| << ", value);\n"; |
| } |
| inl_ << "}\n\n"; |
| } |
| |
| void CppClassGenerator::GenerateFieldAccessorForSmi(const Field& f) { |
| DCHECK(f.name_and_type.type->IsSubtypeOf(TypeOracle::GetSmiType())); |
| // Follow the convention to create Smi accessors with type int. |
| const std::string type = "int"; |
| const std::string& name = f.name_and_type.name; |
| const std::string offset = "k" + CamelifyString(name) + "Offset"; |
| |
| // Generate declarations in header. |
| if (f.index) { |
| hdr_ << " inline " << type << " " << name << "(int i) const;\n"; |
| hdr_ << " inline void set_" << name << "(int i, " << type |
| << " value);\n\n"; |
| } |
| hdr_ << " inline " << type << " " << name << "() const;\n"; |
| hdr_ << " inline void set_" << name << "(" << type << " value);\n\n"; |
| |
| // Generate implementation in inline header. |
| inl_ << "template <class D, class P>\n"; |
| inl_ << type << " " << gen_name_ << "<D, P>::" << name << "("; |
| if (f.index) { |
| inl_ << "int i"; |
| } |
| inl_ << ") const {\n"; |
| if (f.index) { |
| GenerateBoundsDCheck(inl_, "i", type_, f); |
| inl_ << " int offset = " << offset << " + i * kTaggedSize;\n"; |
| inl_ << " return this->template ReadField<Smi>(offset).value();\n"; |
| inl_ << "}\n"; |
| } else { |
| inl_ << " return TaggedField<Smi, " << offset |
| << ">::load(*this).value();\n"; |
| inl_ << "}\n"; |
| } |
| |
| inl_ << "template <class D, class P>\n"; |
| inl_ << "void " << gen_name_ << "<D, P>::set_" << name << "("; |
| if (f.index) { |
| inl_ << "int i, "; |
| } |
| inl_ << type << " value) {\n"; |
| if (f.index) { |
| GenerateBoundsDCheck(inl_, "i", type_, f); |
| inl_ << " int offset = " << offset << " + i * kTaggedSize;\n"; |
| inl_ << " WRITE_FIELD(*this, offset, Smi::FromInt(value));\n"; |
| } else { |
| inl_ << " WRITE_FIELD(*this, " << offset << ", Smi::FromInt(value));\n"; |
| } |
| inl_ << "}\n\n"; |
| } |
| |
| void CppClassGenerator::GenerateFieldAccessorForTagged(const Field& f) { |
| const Type* field_type = f.name_and_type.type; |
| DCHECK(field_type->IsSubtypeOf(TypeOracle::GetTaggedType())); |
| const std::string& name = f.name_and_type.name; |
| std::string offset = "k" + CamelifyString(name) + "Offset"; |
| bool strong_pointer = field_type->IsSubtypeOf(TypeOracle::GetObjectType()); |
| |
| std::string type = field_type->UnhandlifiedCppTypeName(); |
| // Generate declarations in header. |
| if (!field_type->IsClassType() && field_type != TypeOracle::GetObjectType()) { |
| hdr_ << " // Torque type: " << field_type->ToString() << "\n"; |
| } |
| |
| hdr_ << " inline " << type << " " << name << "(" << (f.index ? "int i" : "") |
| << ") const;\n"; |
| hdr_ << " inline " << type << " " << name << "(IsolateRoot isolates" |
| << (f.index ? ", int i" : "") << ") const;\n"; |
| hdr_ << " inline void set_" << name << "(" << (f.index ? "int i, " : "") |
| << type << " value, WriteBarrierMode mode = UPDATE_WRITE_BARRIER);\n\n"; |
| |
| std::string type_check = GenerateRuntimeTypeCheck(field_type, "value"); |
| |
| // Generate implementation in inline header. |
| inl_ << "template <class D, class P>\n"; |
| inl_ << type << " " << gen_name_ << "<D, P>::" << name << "(" |
| << (f.index ? "int i" : "") << ") const {\n"; |
| inl_ << " IsolateRoot isolate = GetIsolateForPtrCompr(*this);\n"; |
| inl_ << " return " << gen_name_ << "::" << name << "(isolate" |
| << (f.index ? ", i" : "") << ");\n"; |
| inl_ << "}\n"; |
| |
| inl_ << "template <class D, class P>\n"; |
| inl_ << type << " " << gen_name_ << "<D, P>::" << name |
| << "(IsolateRoot isolate" << (f.index ? ", int i" : "") << ") const {\n"; |
| |
| // TODO(tebbi): The distinction between relaxed and non-relaxed accesses here |
| // is pretty arbitrary and just tries to preserve what was there before. |
| // It currently doesn't really make a difference due to concurrent marking |
| // turning all loads and stores to be relaxed. We should probably drop the |
| // distinction at some point, even though in principle non-relaxed operations |
| // would give us TSAN protection. |
| if (f.index) { |
| GenerateBoundsDCheck(inl_, "i", type_, f); |
| inl_ << " int offset = " << offset << " + i * kTaggedSize;\n"; |
| inl_ << " auto value = TaggedField<" << type |
| << ">::Relaxed_Load(isolate, *this, offset);\n"; |
| } else { |
| inl_ << " auto value = TaggedField<" << type << ", " << offset |
| << ">::load(isolate, *this);\n"; |
| } |
| if (!type_check.empty()) { |
| inl_ << " DCHECK(" << type_check << ");\n"; |
| } |
| inl_ << " return value;\n"; |
| inl_ << "}\n"; |
| |
| inl_ << "template <class D, class P>\n"; |
| inl_ << "void " << gen_name_ << "<D, P>::set_" << name << "("; |
| if (f.index) { |
| inl_ << "int i, "; |
| } |
| inl_ << type << " value, WriteBarrierMode mode) {\n"; |
| if (!type_check.empty()) { |
| inl_ << " SLOW_DCHECK(" << type_check << ");\n"; |
| } |
| if (f.index) { |
| GenerateBoundsDCheck(inl_, "i", type_, f); |
| const char* write_macro = |
| strong_pointer ? "WRITE_FIELD" : "RELAXED_WRITE_WEAK_FIELD"; |
| inl_ << " int offset = " << offset << " + i * kTaggedSize;\n"; |
| offset = "offset"; |
| inl_ << " " << write_macro << "(*this, offset, value);\n"; |
| } else { |
| const char* write_macro = |
| strong_pointer ? "RELAXED_WRITE_FIELD" : "RELAXED_WRITE_WEAK_FIELD"; |
| inl_ << " " << write_macro << "(*this, " << offset << ", value);\n"; |
| } |
| const char* write_barrier = strong_pointer ? "CONDITIONAL_WRITE_BARRIER" |
| : "CONDITIONAL_WEAK_WRITE_BARRIER"; |
| inl_ << " " << write_barrier << "(*this, " << offset << ", value, mode);\n"; |
| inl_ << "}\n\n"; |
| } |
| |
| void GenerateStructLayoutDescription(std::ostream& header, |
| const StructType* type) { |
| header << "struct TorqueGenerated" << CamelifyString(type->name()) |
| << "Offsets {\n"; |
| for (const Field& field : type->fields()) { |
| header << " static constexpr int k" |
| << CamelifyString(field.name_and_type.name) |
| << "Offset = " << *field.offset << ";\n"; |
| } |
| header << " static constexpr int kSize = " << type->PackedSize() << ";\n"; |
| header << "};\n\n"; |
| } |
| |
| } // namespace |
| |
| void ImplementationVisitor::GenerateClassDefinitions( |
| const std::string& output_directory) { |
| std::stringstream factory_header; |
| std::stringstream factory_impl; |
| std::string factory_basename = "factory"; |
| |
| std::stringstream forward_declarations; |
| std::string forward_declarations_filename = "class-forward-declarations.h"; |
| |
| { |
| factory_impl << "#include \"src/heap/factory.h\"\n"; |
| factory_impl << "#include \"src/heap/factory-inl.h\"\n"; |
| factory_impl << "#include \"src/heap/heap.h\"\n"; |
| factory_impl << "#include \"src/heap/heap-inl.h\"\n"; |
| factory_impl << "#include \"src/execution/isolate.h\"\n"; |
| factory_impl << "#include " |
| "\"src/objects/all-objects-inl.h\"\n\n"; |
| NamespaceScope factory_impl_namespaces(factory_impl, {"v8", "internal"}); |
| factory_impl << "\n"; |
| |
| IncludeGuardScope include_guard(forward_declarations, |
| forward_declarations_filename); |
| NamespaceScope forward_declarations_namespaces(forward_declarations, |
| {"v8", "internal"}); |
| |
| std::set<const StructType*, TypeLess> structs_used_in_classes; |
| |
| // Emit forward declarations. |
| for (const ClassType* type : TypeOracle::GetClasses()) { |
| auto& streams = GlobalContext::GeneratedPerFile(type->AttributedToFile()); |
| std::ostream& header = streams.class_definition_headerfile; |
| header << "class " << type->GetGeneratedTNodeTypeName() << ";\n"; |
| forward_declarations << "class " << type->GetGeneratedTNodeTypeName() |
| << ";\n"; |
| } |
| |
| for (const ClassType* type : TypeOracle::GetClasses()) { |
| auto& streams = GlobalContext::GeneratedPerFile(type->AttributedToFile()); |
| std::ostream& header = streams.class_definition_headerfile; |
| std::ostream& inline_header = streams.class_definition_inline_headerfile; |
| std::ostream& implementation = streams.class_definition_ccfile; |
| |
| if (type->GenerateCppClassDefinitions()) { |
| CppClassGenerator g(type, header, inline_header, implementation); |
| g.GenerateClass(); |
| } |
| for (const Field& f : type->fields()) { |
| const Type* field_type = f.name_and_type.type; |
| if (auto field_as_struct = field_type->StructSupertype()) { |
| structs_used_in_classes.insert(*field_as_struct); |
| } |
| } |
| if (type->ShouldExport() && !type->IsAbstract() && |
| !type->HasCustomMap()) { |
| factory_header << type->HandlifiedCppTypeName() << " New" |
| << type->name() << "("; |
| factory_impl << type->HandlifiedCppTypeName() << " Factory::New" |
| << type->name() << "("; |
| |
| for (const Field& f : type->ComputeAllFields()) { |
| if (f.name_and_type.name == "map") continue; |
| if (!f.index) { |
| std::string type_string = |
| f.name_and_type.type->HandlifiedCppTypeName(); |
| factory_header << type_string << " " << f.name_and_type.name |
| << ", "; |
| factory_impl << type_string << " " << f.name_and_type.name << ", "; |
| } |
| } |
| |
| factory_header << "AllocationType allocation_type);\n"; |
| factory_impl << "AllocationType allocation_type) {\n"; |
| |
| factory_impl << " int size = "; |
| const ClassType* super = type->GetSuperClass(); |
| std::string gen_name = "TorqueGenerated" + type->name(); |
| std::string gen_name_T = |
| gen_name + "<" + type->name() + ", " + super->name() + ">"; |
| factory_impl << gen_name_T << "::SizeFor("; |
| |
| bool first = true; |
| auto index_fields = GetOrderedUniqueIndexFields(*type); |
| CHECK(index_fields.has_value()); |
| for (auto index_field : *index_fields) { |
| if (!first) { |
| factory_impl << ", "; |
| } |
| factory_impl << index_field.name_and_type.name; |
| first = false; |
| } |
| |
| factory_impl << ");\n"; |
| factory_impl << " ReadOnlyRoots roots(isolate());\n"; |
| factory_impl << " HeapObject result =\n"; |
| factory_impl << " " |
| "isolate()->heap()->AllocateRawWith<Heap::kRetryOrFail>" |
| "(size, allocation_type);\n"; |
| factory_impl << " WriteBarrierMode write_barrier_mode =\n" |
| << " allocation_type == AllocationType::kYoung\n" |
| << " ? SKIP_WRITE_BARRIER : UPDATE_WRITE_BARRIER;\n"; |
| factory_impl << " result.set_map_after_allocation(roots." |
| << SnakeifyString(type->name()) |
| << "_map(), write_barrier_mode);\n"; |
| factory_impl << " " << type->HandlifiedCppTypeName() |
| << " result_handle(" << type->name() |
| << "::cast(result), isolate());\n"; |
| |
| for (const Field& f : type->ComputeAllFields()) { |
| if (f.name_and_type.name == "map") continue; |
| if (!f.index) { |
| factory_impl << " result_handle->set_" |
| << SnakeifyString(f.name_and_type.name) << "("; |
| if (f.name_and_type.type->IsSubtypeOf( |
| TypeOracle::GetTaggedType()) && |
| !f.name_and_type.type->IsSubtypeOf(TypeOracle::GetSmiType())) { |
| factory_impl << "*" << f.name_and_type.name |
| << ", write_barrier_mode"; |
| } else { |
| factory_impl << f.name_and_type.name; |
| } |
| factory_impl << ");\n"; |
| } |
| } |
| |
| factory_impl << " return result_handle;\n"; |
| factory_impl << "}\n\n"; |
| } |
| } |
| |
| for (const StructType* type : structs_used_in_classes) { |
| std::ostream& header = |
| GlobalContext::GeneratedPerFile(type->GetPosition().source) |
| .class_definition_headerfile; |
| if (type != TypeOracle::GetFloat64OrHoleType()) { |
| GenerateStructLayoutDescription(header, type); |
| } |
| } |
| } |
| WriteFile(output_directory + "/" + factory_basename + ".inc", |
| factory_header.str()); |
| WriteFile(output_directory + "/" + factory_basename + ".cc", |
| factory_impl.str()); |
| WriteFile(output_directory + "/" + forward_declarations_filename, |
| forward_declarations.str()); |
| } |
| |
| namespace { |
| void GeneratePrintDefinitionsForClass(std::ostream& impl, const ClassType* type, |
| const std::string& gen_name, |
| const std::string& gen_name_T, |
| const std::string template_params) { |
| impl << template_params << "\n"; |
| impl << "void " << gen_name_T << "::" << type->name() |
| << "Print(std::ostream& os) {\n"; |
| impl << " this->PrintHeader(os, \"" << type->name() << "\");\n"; |
| auto hierarchy = type->GetHierarchy(); |
| std::map<std::string, const AggregateType*> field_names; |
| for (const AggregateType* aggregate_type : hierarchy) { |
| for (const Field& f : aggregate_type->fields()) { |
| if (f.name_and_type.name == "map") continue; |
| if (!f.index.has_value()) { |
| if (f.name_and_type.type->IsSubtypeOf(TypeOracle::GetSmiType()) || |
| !f.name_and_type.type->IsSubtypeOf(TypeOracle::GetTaggedType())) { |
| impl << " os << \"\\n - " << f.name_and_type.name << ": \" << "; |
| if (f.name_and_type.type->StructSupertype()) { |
| // TODO(tebbi): Print struct fields too. |
| impl << "\" <struct field printing still unimplemented>\";\n"; |
| } else { |
| impl << "this->" << f.name_and_type.name << "();\n"; |
| } |
| } else { |
| impl << " os << \"\\n - " << f.name_and_type.name << ": \" << " |
| << "Brief(this->" << f.name_and_type.name << "());\n"; |
| } |
| } |
| } |
| } |
| impl << " os << '\\n';\n"; |
| impl << "}\n\n"; |
| } |
| } // namespace |
| |
| void ImplementationVisitor::GeneratePrintDefinitions( |
| const std::string& output_directory) { |
| std::stringstream impl; |
| std::string file_name = "objects-printer.cc"; |
| { |
| IfDefScope object_print(impl, "OBJECT_PRINT"); |
| |
| impl << "#include <iosfwd>\n\n"; |
| impl << "#include \"src/objects/all-objects-inl.h\"\n\n"; |
| |
| NamespaceScope impl_namespaces(impl, {"v8", "internal"}); |
| |
| for (const ClassType* type : TypeOracle::GetClasses()) { |
| if (!type->ShouldGeneratePrint()) continue; |
| |
| if (type->GenerateCppClassDefinitions()) { |
| const ClassType* super = type->GetSuperClass(); |
| std::string gen_name = "TorqueGenerated" + type->name(); |
| std::string gen_name_T = |
| gen_name + "<" + type->name() + ", " + super->name() + ">"; |
| std::string template_decl = "template <>"; |
| GeneratePrintDefinitionsForClass(impl, type, gen_name, gen_name_T, |
| template_decl); |
| } else { |
| GeneratePrintDefinitionsForClass(impl, type, type->name(), type->name(), |
| ""); |
| } |
| } |
| } |
| |
| std::string new_contents(impl.str()); |
| WriteFile(output_directory + "/" + file_name, new_contents); |
| } |
| |
| base::Optional<std::string> MatchSimpleBodyDescriptor(const ClassType* type) { |
| std::vector<ObjectSlotKind> slots = type->ComputeHeaderSlotKinds(); |
| if (!type->HasStaticSize()) { |
| slots.push_back(*type->ComputeArraySlotKind()); |
| } |
| |
| // Skip the map slot. |
| size_t i = 1; |
| while (i < slots.size() && slots[i] == ObjectSlotKind::kNoPointer) ++i; |
| if (i == slots.size()) return "DataOnlyBodyDescriptor"; |
| bool has_weak_pointers = false; |
| size_t start_index = i; |
| for (; i < slots.size(); ++i) { |
| if (slots[i] == ObjectSlotKind::kStrongPointer) { |
| continue; |
| } else if (slots[i] == ObjectSlotKind::kMaybeObjectPointer) { |
| has_weak_pointers = true; |
| } else if (slots[i] == ObjectSlotKind::kNoPointer) { |
| break; |
| } else { |
| return base::nullopt; |
| } |
| } |
| size_t end_index = i; |
| for (; i < slots.size(); ++i) { |
| if (slots[i] != ObjectSlotKind::kNoPointer) return base::nullopt; |
| } |
| size_t start_offset = start_index * TargetArchitecture::TaggedSize(); |
| size_t end_offset = end_index * TargetArchitecture::TaggedSize(); |
| // We pick a suffix-range body descriptor even in cases where the object size |
| // is fixed, to reduce the amount of code executed for object visitation. |
| if (end_index == slots.size()) { |
| return ToString("SuffixRange", has_weak_pointers ? "Weak" : "", |
| "BodyDescriptor<", start_offset, ">"); |
| } |
| if (!has_weak_pointers) { |
| return ToString("FixedRangeDescriptor<", start_offset, ", ", end_offset, |
| ", ", *type->size().SingleValue(), ">"); |
| } |
| return base::nullopt; |
| } |
| |
| void ImplementationVisitor::GenerateBodyDescriptors( |
| const std::string& output_directory) { |
| std::string file_name = "objects-body-descriptors-inl.inc"; |
| std::stringstream h_contents; |
| |
| for (const ClassType* type : TypeOracle::GetClasses()) { |
| std::string name = type->name(); |
| if (!type->ShouldGenerateBodyDescriptor()) continue; |
| |
| bool has_array_fields = !type->HasStaticSize(); |
| std::vector<ObjectSlotKind> header_slot_kinds = |
| type->ComputeHeaderSlotKinds(); |
| base::Optional<ObjectSlotKind> array_slot_kind = |
| type->ComputeArraySlotKind(); |
| DCHECK_EQ(has_array_fields, array_slot_kind.has_value()); |
| |
| h_contents << "class " << name << "::BodyDescriptor final : public "; |
| if (auto descriptor_name = MatchSimpleBodyDescriptor(type)) { |
| h_contents << *descriptor_name << " {\n"; |
| h_contents << " public:\n"; |
| } else { |
| h_contents << "BodyDescriptorBase {\n"; |
| h_contents << " public:\n"; |
| |
| h_contents << " static bool IsValidSlot(Map map, HeapObject obj, int " |
| "offset) {\n"; |
| if (has_array_fields) { |
| h_contents << " if (offset < kHeaderSize) {\n"; |
| } |
| h_contents << " bool valid_slots[] = {"; |
| for (ObjectSlotKind slot : header_slot_kinds) { |
| h_contents << (slot != ObjectSlotKind::kNoPointer ? "1" : "0") << ","; |
| } |
| h_contents << "};\n" |
| << " return valid_slots[static_cast<unsigned " |
| "int>(offset)/kTaggedSize];\n"; |
| if (has_array_fields) { |
| h_contents << " }\n"; |
| bool array_is_tagged = *array_slot_kind != ObjectSlotKind::kNoPointer; |
| h_contents << " return " << (array_is_tagged ? "true" : "false") |
| << ";\n"; |
| } |
| h_contents << " }\n\n"; |
| |
| h_contents << " template <typename ObjectVisitor>\n"; |
| h_contents |
| << " static inline void IterateBody(Map map, HeapObject obj, " |
| "int object_size, ObjectVisitor* v) {\n"; |
| |
| std::vector<ObjectSlotKind> slots = std::move(header_slot_kinds); |
| if (has_array_fields) slots.push_back(*array_slot_kind); |
| |
| // Skip the map slot. |
| slots.erase(slots.begin()); |
| size_t start_offset = TargetArchitecture::TaggedSize(); |
| |
| size_t end_offset = start_offset; |
| ObjectSlotKind section_kind; |
| for (size_t i = 0; i <= slots.size(); ++i) { |
| base::Optional<ObjectSlotKind> next_section_kind; |
| bool finished_section = false; |
| if (i == 0) { |
| next_section_kind = slots[i]; |
| } else if (i < slots.size()) { |
| if (auto combined = Combine(section_kind, slots[i])) { |
| next_section_kind = *combined; |
| } else { |
| next_section_kind = slots[i]; |
| finished_section = true; |
| } |
| } else { |
| finished_section = true; |
| } |
| if (finished_section) { |
| bool is_array_slot = i == slots.size() && has_array_fields; |
| bool multiple_slots = |
| is_array_slot || |
| (end_offset - start_offset > TargetArchitecture::TaggedSize()); |
| base::Optional<std::string> iterate_command; |
| switch (section_kind) { |
| case ObjectSlotKind::kStrongPointer: |
| iterate_command = "IteratePointer"; |
| break; |
| case ObjectSlotKind::kMaybeObjectPointer: |
| iterate_command = "IterateMaybeWeakPointer"; |
| break; |
| case ObjectSlotKind::kCustomWeakPointer: |
| iterate_command = "IterateCustomWeakPointer"; |
| break; |
| case ObjectSlotKind::kNoPointer: |
| break; |
| } |
| if (iterate_command) { |
| if (multiple_slots) *iterate_command += "s"; |
| h_contents << " " << *iterate_command << "(obj, " |
| << start_offset; |
| if (multiple_slots) { |
| h_contents << ", " |
| << (i == slots.size() ? "object_size" |
| : std::to_string(end_offset)); |
| } |
| h_contents << ", v);\n"; |
| } |
| start_offset = end_offset; |
| } |
| if (i < slots.size()) section_kind = *next_section_kind; |
| end_offset += TargetArchitecture::TaggedSize(); |
| } |
| |
| h_contents << " }\n\n"; |
| } |
| |
| h_contents |
| << " static inline int SizeOf(Map map, HeapObject raw_object) {\n"; |
| if (type->size().SingleValue()) { |
| h_contents << " return " << *type->size().SingleValue() << ";\n"; |
| } else { |
| // We use an unchecked_cast here because this is used for concurrent |
| // marking, where we shouldn't re-read the map. |
| h_contents << " return " << name |
| << "::unchecked_cast(raw_object).AllocatedSize();\n"; |
| } |
| h_contents << " }\n\n"; |
| |
| h_contents << "};\n"; |
| } |
| |
| WriteFile(output_directory + "/" + file_name, h_contents.str()); |
| } |
| |
| namespace { |
| |
| // Generate verification code for a single piece of class data, which might be |
| // nested within a struct or might be a single element in an indexed field (or |
| // both). |
| void GenerateFieldValueVerifier(const std::string& class_name, bool indexed, |
| std::string offset, const Field& leaf_field, |
| std::string indexed_field_size, |
| std::ostream& cc_contents) { |
| const Type* field_type = leaf_field.name_and_type.type; |
| |
| bool maybe_object = |
| !field_type->IsSubtypeOf(TypeOracle::GetStrongTaggedType()); |
| const char* object_type = maybe_object ? "MaybeObject" : "Object"; |
| const char* verify_fn = |
| maybe_object ? "VerifyMaybeObjectPointer" : "VerifyPointer"; |
| if (indexed) { |
| offset += " + i * " + indexed_field_size; |
| } |
| // Name the local var based on the field name for nicer CHECK output. |
| const std::string value = leaf_field.name_and_type.name + "__value"; |
| |
| // Read the field. |
| cc_contents << " " << object_type << " " << value << " = TaggedField<" |
| << object_type << ">::load(o, " << offset << ");\n"; |
| |
| // Call VerifyPointer or VerifyMaybeObjectPointer on it. |
| cc_contents << " " << object_type << "::" << verify_fn << "(isolate, " |
| << value << ");\n"; |
| |
| // Check that the value is of an appropriate type. We can skip this part for |
| // the Object type because it would not check anything beyond what we already |
| // checked with VerifyPointer. |
| if (field_type != TypeOracle::GetObjectType()) { |
| cc_contents << " CHECK(" << GenerateRuntimeTypeCheck(field_type, value) |
| << ");\n"; |
| } |
| } |
| |
| void GenerateClassFieldVerifier(const std::string& class_name, |
| const ClassType& class_type, const Field& f, |
| std::ostream& h_contents, |
| std::ostream& cc_contents) { |
| if (!f.generate_verify) return; |
| const Type* field_type = f.name_and_type.type; |
| |
| // We only verify tagged types, not raw numbers or pointers. Structs |
| // consisting of tagged types are also included. |
| if (!field_type->IsSubtypeOf(TypeOracle::GetTaggedType()) && |
| !field_type->StructSupertype()) |
| return; |
| if (field_type == TypeOracle::GetFloat64OrHoleType()) return; |
| // Do not verify if the field may be uninitialized. |
| if (TypeOracle::GetUninitializedType()->IsSubtypeOf(field_type)) return; |
| |
| std::string field_start_offset; |
| if (f.index) { |
| field_start_offset = f.name_and_type.name + "__offset"; |
| std::string length = f.name_and_type.name + "__length"; |
| cc_contents << " intptr_t " << field_start_offset << ", " << length |
| << ";\n"; |
| cc_contents << " std::tie(std::ignore, " << field_start_offset << ", " |
| << length << ") = " |
| << Callable::PrefixNameForCCOutput( |
| class_type.GetSliceMacroName(f)) |
| << "(isolate, o);\n"; |
| |
| // Slices use intptr, but TaggedField<T>.load() uses int, so verify that |
| // such a cast is valid. |
| cc_contents << " CHECK_EQ(" << field_start_offset << ", static_cast<int>(" |
| << field_start_offset << "));\n"; |
| cc_contents << " CHECK_EQ(" << length << ", static_cast<int>(" << length |
| << "));\n"; |
| field_start_offset = "static_cast<int>(" + field_start_offset + ")"; |
| length = "static_cast<int>(" + length + ")"; |
| |
| cc_contents << " for (int i = 0; i < " << length << "; ++i) {\n"; |
| } else { |
| // Non-indexed fields have known offsets. |
| field_start_offset = std::to_string(*f.offset); |
| cc_contents << " {\n"; |
| } |
| |
| if (auto struct_type = field_type->StructSupertype()) { |
| for (const Field& struct_field : (*struct_type)->fields()) { |
| if (struct_field.name_and_type.type->IsSubtypeOf( |
| TypeOracle::GetTaggedType())) { |
| GenerateFieldValueVerifier( |
| class_name, f.index.has_value(), |
| field_start_offset + " + " + std::to_string(*struct_field.offset), |
| struct_field, std::to_string((*struct_type)->PackedSize()), |
| cc_contents); |
| } |
| } |
| } else { |
| GenerateFieldValueVerifier(class_name, f.index.has_value(), |
| field_start_offset, f, "kTaggedSize", |
| cc_contents); |
| } |
| |
| cc_contents << " }\n"; |
| } |
| |
| } // namespace |
| |
| void ImplementationVisitor::GenerateClassVerifiers( |
| const std::string& output_directory) { |
| std::string file_name = "class-verifiers"; |
| std::stringstream h_contents; |
| std::stringstream cc_contents; |
| { |
| IncludeGuardScope include_guard(h_contents, file_name + ".h"); |
| IfDefScope verify_heap_h(h_contents, "VERIFY_HEAP"); |
| IfDefScope verify_heap_cc(cc_contents, "VERIFY_HEAP"); |
| |
| cc_contents << "\n#include \"src/objects/objects.h\"\n"; |
| |
| for (const std::string& include_path : GlobalContext::CppIncludes()) { |
| cc_contents << "#include " << StringLiteralQuote(include_path) << "\n"; |
| } |
| cc_contents << "#include \"torque-generated/" << file_name << ".h\"\n"; |
| cc_contents << "#include " |
| "\"src/objects/all-objects-inl.h\"\n"; |
| cc_contents << "#include \"torque-generated/runtime-macros.h\"\n"; |
| |
| IncludeObjectMacrosScope object_macros(cc_contents); |
| |
| NamespaceScope h_namespaces(h_contents, {"v8", "internal"}); |
| NamespaceScope cc_namespaces(cc_contents, {"v8", "internal"}); |
| |
| // Generate forward declarations to avoid including any headers. |
| h_contents << "class Isolate;\n"; |
| for (const ClassType* type : TypeOracle::GetClasses()) { |
| if (!type->ShouldGenerateVerify()) continue; |
| h_contents << "class " << type->name() << ";\n"; |
| } |
| |
| const char* verifier_class = "TorqueGeneratedClassVerifiers"; |
| |
| h_contents << "class V8_EXPORT_PRIVATE " << verifier_class << "{\n"; |
| h_contents << " public:\n"; |
| |
| for (const ClassType* type : TypeOracle::GetClasses()) { |
| std::string name = type->name(); |
| if (!type->ShouldGenerateVerify()) continue; |
| |
| std::string method_name = name + "Verify"; |
| |
| h_contents << " static void " << method_name << "(" << name |
| << " o, Isolate* isolate);\n"; |
| |
| cc_contents << "void " << verifier_class << "::" << method_name << "(" |
| << name << " o, Isolate* isolate) {\n"; |
| |
| // First, do any verification for the super class. Not all classes have |
| // verifiers, so skip to the nearest super class that has one. |
| const ClassType* super_type = type->GetSuperClass(); |
| while (super_type && !super_type->ShouldGenerateVerify()) { |
| super_type = super_type->GetSuperClass(); |
| } |
| if (super_type) { |
| std::string super_name = super_type->name(); |
| if (super_name == "HeapObject") { |
| // Special case: HeapObjectVerify checks the Map type and dispatches |
| // to more specific types, so calling it here would cause infinite |
| // recursion. We could consider moving that behavior into a |
| // different method to make the contract of *Verify methods more |
| // consistent, but for now we'll just avoid the bad case. |
| cc_contents << " " << super_name << "Verify(o, isolate);\n"; |
| } else { |
| cc_contents << " o." << super_name << "Verify(isolate);\n"; |
| } |
| } |
| |
| // Second, verify that this object is what it claims to be. |
| cc_contents << " CHECK(o.Is" << name << "());\n"; |
| |
| // Third, verify its properties. |
| for (auto f : type->fields()) { |
| GenerateClassFieldVerifier(name, *type, f, h_contents, cc_contents); |
| } |
| |
| cc_contents << "}\n"; |
| } |
| |
| h_contents << "};\n"; |
| } |
| WriteFile(output_directory + "/" + file_name + ".h", h_contents.str()); |
| WriteFile(output_directory + "/" + file_name + ".cc", cc_contents.str()); |
| } |
| |
| void ImplementationVisitor::GenerateEnumVerifiers( |
| const std::string& output_directory) { |
| std::string file_name = "enum-verifiers"; |
| std::stringstream cc_contents; |
| { |
| cc_contents << "#include \"src/compiler/code-assembler.h\"\n"; |
| for (const std::string& include_path : GlobalContext::CppIncludes()) { |
| cc_contents << "#include " << StringLiteralQuote(include_path) << "\n"; |
| } |
| cc_contents << "\n"; |
| |
| NamespaceScope cc_namespaces(cc_contents, {"v8", "internal", ""}); |
| |
| cc_contents << "class EnumVerifier {\n"; |
| for (const auto& desc : GlobalContext::Get().ast()->EnumDescriptions()) { |
| cc_contents << " // " << desc.name << " (" << desc.pos << ")\n"; |
| cc_contents << " void VerifyEnum_" << desc.name << "(" |
| << desc.constexpr_generates |
| << " x) {\n" |
| " switch(x) {\n"; |
| for (const auto& entry : desc.entries) { |
| cc_contents << " case " << entry << ": break;\n"; |
| } |
| if (desc.is_open) cc_contents << " default: break;\n"; |
| cc_contents << " }\n }\n\n"; |
| } |
| cc_contents << "};\n"; |
| } |
| |
| WriteFile(output_directory + "/" + file_name + ".cc", cc_contents.str()); |
| } |
| |
| void ImplementationVisitor::GenerateExportedMacrosAssembler( |
| const std::string& output_directory) { |
| std::string file_name = "exported-macros-assembler"; |
| std::stringstream h_contents; |
| std::stringstream cc_contents; |
| { |
| IncludeGuardScope include_guard(h_contents, file_name + ".h"); |
| |
| h_contents << "#include \"src/compiler/code-assembler.h\"\n"; |
| h_contents << "#include \"src/execution/frames.h\"\n"; |
| h_contents << "#include \"torque-generated/csa-types.h\"\n"; |
| cc_contents << "#include \"src/objects/fixed-array-inl.h\"\n"; |
| cc_contents << "#include \"src/objects/free-space.h\"\n"; |
| cc_contents << "#include \"src/objects/js-regexp-string-iterator.h\"\n"; |
| cc_contents << "#include \"src/objects/ordered-hash-table.h\"\n"; |
| cc_contents << "#include \"src/objects/property-descriptor-object.h\"\n"; |
| cc_contents << "#include \"src/objects/synthetic-module.h\"\n"; |
| cc_contents << "#include \"src/objects/template-objects.h\"\n"; |
| { |
| IfDefScope intl_scope(cc_contents, "V8_INTL_SUPPORT"); |
| cc_contents << "#include \"src/objects/js-break-iterator.h\"\n"; |
| cc_contents << "#include \"src/objects/js-collator.h\"\n"; |
| cc_contents << "#include \"src/objects/js-date-time-format.h\"\n"; |
| cc_contents << "#include \"src/objects/js-display-names.h\"\n"; |
| cc_contents << "#include \"src/objects/js-list-format.h\"\n"; |
| cc_contents << "#include \"src/objects/js-locale.h\"\n"; |
| cc_contents << "#include \"src/objects/js-number-format.h\"\n"; |
| cc_contents << "#include \"src/objects/js-plural-rules.h\"\n"; |
| cc_contents << "#include \"src/objects/js-relative-time-format.h\"\n"; |
| cc_contents << "#include \"src/objects/js-segment-iterator.h\"\n"; |
| cc_contents << "#include \"src/objects/js-segmenter.h\"\n"; |
| cc_contents << "#include \"src/objects/js-segments.h\"\n"; |
| } |
| cc_contents << "#include \"torque-generated/" << file_name << ".h\"\n"; |
| |
| for (SourceId file : SourceFileMap::AllSources()) { |
| cc_contents << "#include \"torque-generated/" + |
| SourceFileMap::PathFromV8RootWithoutExtension(file) + |
| "-tq-csa.h\"\n"; |
| } |
| |
| NamespaceScope h_namespaces(h_contents, {"v8", "internal"}); |
| NamespaceScope cc_namespaces(cc_contents, {"v8", "internal"}); |
| |
| h_contents << "class V8_EXPORT_PRIVATE " |
| "TorqueGeneratedExportedMacrosAssembler {\n" |
| << " public:\n" |
| << " explicit TorqueGeneratedExportedMacrosAssembler" |
| "(compiler::CodeAssemblerState* state) : state_(state) {\n" |
| << " USE(state_);\n" |
| << " }\n"; |
| |
| for (auto& declarable : GlobalContext::AllDeclarables()) { |
| TorqueMacro* macro = TorqueMacro::DynamicCast(declarable.get()); |
| if (!(macro && macro->IsExportedToCSA())) continue; |
| |
| h_contents << " "; |
| GenerateFunctionDeclaration(h_contents, "", macro->ReadableName(), |
| macro->signature(), macro->parameter_names(), |
| false); |
| h_contents << ";\n"; |
| |
| std::vector<std::string> parameter_names = GenerateFunctionDeclaration( |
| cc_contents, |
| "TorqueGeneratedExportedMacrosAssembler::", macro->ReadableName(), |
| macro->signature(), macro->parameter_names(), false); |
| cc_contents << "{\n"; |
| cc_contents << "return " << macro->ExternalName() << "(state_"; |
| for (auto& name : parameter_names) { |
| cc_contents << ", " << name; |
| } |
| cc_contents << ");\n"; |
| cc_contents << "}\n"; |
| } |
| |
| h_contents << " private:\n" |
| << " compiler::CodeAssemblerState* state_;\n" |
| << "};\n"; |
| } |
| WriteFile(output_directory + "/" + file_name + ".h", h_contents.str()); |
| WriteFile(output_directory + "/" + file_name + ".cc", cc_contents.str()); |
| } |
| |
| void ImplementationVisitor::GenerateCSATypes( |
| const std::string& output_directory) { |
| std::string file_name = "csa-types"; |
| std::stringstream h_contents; |
| { |
| IncludeGuardScope include_guard(h_contents, file_name + ".h"); |
| h_contents << "#include \"src/compiler/code-assembler.h\"\n\n"; |
| |
| NamespaceScope h_namespaces(h_contents, {"v8", "internal"}); |
| |
| // Generates headers for all structs in a topologically-sorted order, since |
| // TypeOracle keeps them in the order of their resolution |
| for (const auto& type : TypeOracle::GetAggregateTypes()) { |
| const StructType* struct_type = StructType::DynamicCast(type.get()); |
| if (!struct_type) continue; |
| h_contents << "struct " << struct_type->GetGeneratedTypeNameImpl() |
| << " {\n"; |
| for (auto& field : struct_type->fields()) { |
| h_contents << " " << field.name_and_type.type->GetGeneratedTypeName(); |
| h_contents << " " << field.name_and_type.name << ";\n"; |
| } |
| h_contents << "\n std::tuple<"; |
| bool first = true; |
| for (const Type* type : LowerType(struct_type)) { |
| if (!first) { |
| h_contents << ", "; |
| } |
| first = false; |
| h_contents << type->GetGeneratedTypeName(); |
| } |
| h_contents << "> Flatten() const {\n" |
| << " return std::tuple_cat("; |
| first = true; |
| for (auto& field : struct_type->fields()) { |
| if (!first) { |
| h_contents << ", "; |
| } |
| first = false; |
| if (field.name_and_type.type->StructSupertype()) { |
| h_contents << field.name_and_type.name << ".Flatten()"; |
| } else { |
| h_contents << "std::make_tuple(" << field.name_and_type.name << ")"; |
| } |
| } |
| h_contents << ");\n"; |
| h_contents << " }\n"; |
| h_contents << "};\n"; |
| } |
| } |
| WriteFile(output_directory + "/" + file_name + ".h", h_contents.str()); |
| } |
| |
| void ReportAllUnusedMacros() { |
| for (const auto& declarable : GlobalContext::AllDeclarables()) { |
| if (!declarable->IsMacro() || declarable->IsExternMacro()) continue; |
| |
| Macro* macro = Macro::cast(declarable.get()); |
| if (macro->IsUsed()) continue; |
| |
| if (macro->IsTorqueMacro() && TorqueMacro::cast(macro)->IsExportedToCSA()) { |
| continue; |
| } |
| // TODO(gsps): Mark methods of generic structs used if they are used in any |
| // instantiation |
| if (Method* method = Method::DynamicCast(macro)) { |
| if (StructType* struct_type = |
| StructType::DynamicCast(method->aggregate_type())) { |
| if (struct_type->GetSpecializedFrom().has_value()) { |
| continue; |
| } |
| } |
| } |
| |
| std::vector<std::string> ignored_prefixes = {"Convert<", "Cast<", |
| "FromConstexpr<"}; |
| const std::string name = macro->ReadableName(); |
| const bool ignore = |
| std::any_of(ignored_prefixes.begin(), ignored_prefixes.end(), |
| [&name](const std::string& prefix) { |
| return StringStartsWith(name, prefix); |
| }); |
| |
| if (!ignore) { |
| Lint("Macro '", macro->ReadableName(), "' is never used.") |
| .Position(macro->IdentifierPosition()); |
| } |
| } |
| } |
| |
| } // namespace torque |
| } // namespace internal |
| } // namespace v8 |