[torque] Add LazyNode support
This change adds a new abstract type Lazy<T> which can be used to interoperate with CSA code that uses LazyNode. This new type has special code-generation rules because its generated type is not TNode<...> but std::function<TNode<...>()>. Torque code can do nothing with this type except pass it around, but passing it to the CSA function RunLazy is an easy way to execute the std::function and get back a normal value. Torque code can also create Lazy<T> values using the intrinsic function %MakeLazy, which takes the name of a macro as its first parameter, followed by arguments to that macro which will be passed when the LazyNode is evaluated. We use the macro's name because the language doesn't support taking references to macros, and implementing such a feature would be complicated. Bug: v8:7793 Change-Id: I09120960e3492dd51be0d4c57e14ff3826b99262 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2701752 Commit-Queue: Seth Brenith <seth.brenith@microsoft.com> Reviewed-by: Nico Hartmann <nicohartmann@chromium.org> Reviewed-by: Leszek Swirski <leszeks@chromium.org> Cr-Commit-Position: refs/heads/master@{#72964}
This commit is contained in:
parent
ef1ae61976
commit
ede2740711
@ -112,6 +112,32 @@ type bool generates 'TNode<BoolT>' constexpr 'bool';
|
||||
type bint generates 'TNode<BInt>' constexpr 'BInt';
|
||||
type string constexpr 'const char*';
|
||||
|
||||
// Represents a std::function which produces the generated TNode type of T.
|
||||
// Useful for passing values to and from CSA code that uses LazyNode<T>, which
|
||||
// is a typedef for std::function<TNode<T>()>. Can be created with %MakeLazy and
|
||||
// accessed with RunLazy.
|
||||
type Lazy<T: type>;
|
||||
|
||||
// Makes a Lazy. The first parameter is the name of a macro, which is looked up
|
||||
// in the context where %MakeLazy is called, as a workaround for the fact that
|
||||
// macros can't be used as values directly. The other parameters are saved and
|
||||
// passed to the macro when somebody runs the resulting Lazy object. Torque
|
||||
// syntax doesn't allow for arbitrary-length generic macros, but the internals
|
||||
// support any number of parameters, so if you need more parameters, feel free
|
||||
// to add additional declarations here.
|
||||
intrinsic %MakeLazy<T: type>(getter: constexpr string): Lazy<T>;
|
||||
intrinsic %MakeLazy<T: type, A1: type>(
|
||||
getter: constexpr string, arg1: A1): Lazy<T>;
|
||||
intrinsic %MakeLazy<T: type, A1: type, A2: type>(
|
||||
getter: constexpr string, arg1: A1, arg2: A2): Lazy<T>;
|
||||
intrinsic %MakeLazy<T: type, A1: type, A2: type, A3: type>(
|
||||
getter: constexpr string, arg1: A1, arg2: A2, arg3: A3): Lazy<T>;
|
||||
|
||||
// Executes a Lazy and returns the result. The CSA-side definition is a
|
||||
// template, but Torque doesn't understand how to use templates for extern
|
||||
// macros, so just add whatever overload definitions you need here.
|
||||
extern macro RunLazy(Lazy<Smi>): Smi;
|
||||
|
||||
// A Smi value containing a bitfield struct as its integer data.
|
||||
@useParentTypeChecker type SmiTagged<T : type extends uint31> extends Smi;
|
||||
|
||||
|
@ -444,6 +444,11 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
|
||||
return CAST(heap_object);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
TNode<T> RunLazy(LazyNode<T> lazy) {
|
||||
return lazy();
|
||||
}
|
||||
|
||||
#define PARAMETER_BINOP(OpName, IntPtrOpName, SmiOpName) \
|
||||
TNode<Smi> OpName(TNode<Smi> a, TNode<Smi> b) { return SmiOpName(a, b); } \
|
||||
TNode<IntPtrT> OpName(TNode<IntPtrT> a, TNode<IntPtrT> b) { \
|
||||
|
@ -261,6 +261,11 @@ void CCGenerator::EmitInstruction(
|
||||
ReportError("Not supported in C++ output: CallCsaMacroAndBranch");
|
||||
}
|
||||
|
||||
void CCGenerator::EmitInstruction(const MakeLazyNodeInstruction& instruction,
|
||||
Stack<std::string>* stack) {
|
||||
ReportError("Not supported in C++ output: MakeLazyNode");
|
||||
}
|
||||
|
||||
void CCGenerator::EmitInstruction(const CallBuiltinInstruction& instruction,
|
||||
Stack<std::string>* stack) {
|
||||
ReportError("Not supported in C++ output: CallBuiltin");
|
||||
|
@ -73,6 +73,7 @@ static const char* const MUTABLE_SLICE_TYPE_STRING = "MutableSlice";
|
||||
static const char* const CONST_SLICE_TYPE_STRING = "ConstSlice";
|
||||
static const char* const WEAK_TYPE_STRING = "Weak";
|
||||
static const char* const SMI_TAGGED_TYPE_STRING = "SmiTagged";
|
||||
static const char* const LAZY_TYPE_STRING = "Lazy";
|
||||
static const char* const UNINITIALIZED_ITERATOR_TYPE_STRING =
|
||||
"UninitializedIterator";
|
||||
static const char* const GENERIC_TYPE_INSTANTIATION_NAMESPACE_STRING =
|
||||
|
@ -479,6 +479,41 @@ void CSAGenerator::EmitInstruction(
|
||||
}
|
||||
}
|
||||
|
||||
void CSAGenerator::EmitInstruction(const MakeLazyNodeInstruction& instruction,
|
||||
Stack<std::string>* stack) {
|
||||
TypeVector parameter_types =
|
||||
instruction.macro->signature().parameter_types.types;
|
||||
std::vector<std::string> args = ProcessArgumentsCommon(
|
||||
parameter_types, instruction.constexpr_arguments, stack);
|
||||
|
||||
std::string result_name =
|
||||
DefinitionToVariable(instruction.GetValueDefinition());
|
||||
|
||||
stack->Push(result_name);
|
||||
|
||||
decls() << " " << instruction.result_type->GetGeneratedTypeName() << " "
|
||||
<< result_name << ";\n";
|
||||
|
||||
// We assume here that the CodeAssemblerState will outlive any usage of
|
||||
// the generated std::function that binds it. Likewise, copies of TNode values
|
||||
// are only valid during generation of the current builtin.
|
||||
out() << " " << result_name << " = [=] () { return ";
|
||||
bool first = true;
|
||||
if (const ExternMacro* extern_macro =
|
||||
ExternMacro::DynamicCast(instruction.macro)) {
|
||||
out() << extern_macro->external_assembler_name() << "(state_)."
|
||||
<< extern_macro->ExternalName() << "(";
|
||||
} else {
|
||||
out() << instruction.macro->ExternalName() << "(state_";
|
||||
first = false;
|
||||
}
|
||||
if (!args.empty()) {
|
||||
if (!first) out() << ", ";
|
||||
PrintCommaSeparatedList(out(), args);
|
||||
}
|
||||
out() << "); };\n";
|
||||
}
|
||||
|
||||
void CSAGenerator::EmitInstruction(const CallBuiltinInstruction& instruction,
|
||||
Stack<std::string>* stack) {
|
||||
std::vector<std::string> arguments = stack->PopMany(instruction.argc);
|
||||
@ -1012,7 +1047,7 @@ void CSAGenerator::EmitCSAValue(VisitResult result,
|
||||
out << "}";
|
||||
} else {
|
||||
DCHECK_EQ(1, result.stack_range().Size());
|
||||
out << "TNode<" << result.type()->GetGeneratedTNodeTypeName() << ">{"
|
||||
out << result.type()->GetGeneratedTypeName() << "{"
|
||||
<< values.Peek(result.stack_range().begin()) << "}";
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,18 @@ namespace torque {
|
||||
|
||||
DEFINE_CONTEXTUAL_VARIABLE(CurrentScope)
|
||||
|
||||
QualifiedName QualifiedName::Parse(std::string qualified_name) {
|
||||
std::vector<std::string> qualifications;
|
||||
while (true) {
|
||||
size_t namespace_delimiter_index = qualified_name.find("::");
|
||||
if (namespace_delimiter_index == std::string::npos) break;
|
||||
qualifications.push_back(
|
||||
qualified_name.substr(0, namespace_delimiter_index));
|
||||
qualified_name = qualified_name.substr(namespace_delimiter_index + 2);
|
||||
}
|
||||
return QualifiedName(qualifications, qualified_name);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const QualifiedName& name) {
|
||||
for (const std::string& qualifier : name.namespace_qualification) {
|
||||
os << qualifier << "::";
|
||||
|
@ -36,6 +36,8 @@ struct QualifiedName {
|
||||
explicit QualifiedName(std::string name)
|
||||
: QualifiedName({}, std::move(name)) {}
|
||||
|
||||
static QualifiedName Parse(std::string qualified_name);
|
||||
|
||||
bool HasNamespaceQualification() const {
|
||||
return !namespace_qualification.empty();
|
||||
}
|
||||
|
@ -2935,6 +2935,68 @@ VisitResult ImplementationVisitor::GenerateCall(
|
||||
const Field& field =
|
||||
class_type->LookupField(StringLiteralUnquote(constexpr_arguments[0]));
|
||||
return GenerateArrayLength(VisitResult(type, argument_range), field);
|
||||
} else if (intrinsic->ExternalName() == "%MakeLazy") {
|
||||
if (specialization_types[0]->IsStructType()) {
|
||||
ReportError("%MakeLazy can't use macros that return structs");
|
||||
}
|
||||
std::string getter_name = StringLiteralUnquote(constexpr_arguments[0]);
|
||||
|
||||
// Normally the parser would split namespace names for us, but we
|
||||
// sidestepped it by putting the macro name in a string literal.
|
||||
QualifiedName qualified_getter_name = QualifiedName::Parse(getter_name);
|
||||
|
||||
// converted_arguments contains all of the arguments to %MakeLazy. We're
|
||||
// looking for a function that takes all but the first.
|
||||
Arguments arguments_to_getter;
|
||||
arguments_to_getter.parameters.insert(
|
||||
arguments_to_getter.parameters.begin(),
|
||||
converted_arguments.begin() + 1, converted_arguments.end());
|
||||
|
||||
Callable* callable = LookupCallable(
|
||||
qualified_getter_name, Declarations::Lookup(qualified_getter_name),
|
||||
arguments_to_getter, {});
|
||||
Macro* getter = Macro::DynamicCast(callable);
|
||||
if (!getter || getter->IsMethod()) {
|
||||
ReportError(
|
||||
"%MakeLazy expects a macro, not builtin or other type of callable");
|
||||
}
|
||||
if (!getter->signature().labels.empty()) {
|
||||
ReportError("%MakeLazy requires a macro with no labels");
|
||||
}
|
||||
if (!getter->signature().return_type->IsSubtypeOf(
|
||||
specialization_types[0])) {
|
||||
ReportError("%MakeLazy expected return type ", *specialization_types[0],
|
||||
" but found ", *getter->signature().return_type);
|
||||
}
|
||||
if (getter->signature().implicit_count > 0) {
|
||||
ReportError("Implicit parameters are not yet supported in %MakeLazy");
|
||||
}
|
||||
|
||||
getter->SetUsed(); // Prevent warnings about unused macros.
|
||||
|
||||
// Now that we've looked up the getter macro, we have to convert the
|
||||
// arguments again, so that, for example, constexpr arguments can be
|
||||
// coerced to non-constexpr types and put on the stack.
|
||||
|
||||
std::vector<VisitResult> converted_arguments_for_getter;
|
||||
StackRange argument_range_for_getter = assembler().TopRange(0);
|
||||
std::vector<std::string> constexpr_arguments_for_getter;
|
||||
|
||||
size_t current = 0;
|
||||
for (auto arg : arguments_to_getter.parameters) {
|
||||
DCHECK_LT(current, getter->signature().types().size());
|
||||
const Type* to_type = getter->signature().types()[current++];
|
||||
AddCallParameter(getter, arg, to_type, &converted_arguments_for_getter,
|
||||
&argument_range_for_getter,
|
||||
&constexpr_arguments_for_getter,
|
||||
/*inline_macro=*/false);
|
||||
}
|
||||
|
||||
// Now that the arguments are prepared, emit the instruction that consumes
|
||||
// them.
|
||||
assembler().Emit(MakeLazyNodeInstruction{getter, return_type,
|
||||
constexpr_arguments_for_getter});
|
||||
return VisitResult(return_type, assembler().TopRange(1));
|
||||
} else {
|
||||
assembler().Emit(CallIntrinsicInstruction{intrinsic, specialization_types,
|
||||
constexpr_arguments});
|
||||
|
@ -663,6 +663,36 @@ DefinitionLocation StoreBitFieldInstruction::GetValueDefinition() const {
|
||||
return DefinitionLocation::Instruction(this, 0);
|
||||
}
|
||||
|
||||
void MakeLazyNodeInstruction::TypeInstruction(Stack<const Type*>* stack,
|
||||
ControlFlowGraph* cfg) const {
|
||||
std::vector<const Type*> parameter_types =
|
||||
LowerParameterTypes(macro->signature().parameter_types);
|
||||
for (intptr_t i = parameter_types.size() - 1; i >= 0; --i) {
|
||||
const Type* arg_type = stack->Pop();
|
||||
const Type* parameter_type = parameter_types.back();
|
||||
parameter_types.pop_back();
|
||||
if (arg_type != parameter_type) {
|
||||
ReportError("parameter ", i, ": expected type ", *parameter_type,
|
||||
" but found type ", *arg_type);
|
||||
}
|
||||
}
|
||||
|
||||
stack->Push(result_type);
|
||||
}
|
||||
|
||||
void MakeLazyNodeInstruction::RecomputeDefinitionLocations(
|
||||
Stack<DefinitionLocation>* locations, Worklist<Block*>* worklist) const {
|
||||
auto parameter_types =
|
||||
LowerParameterTypes(macro->signature().parameter_types);
|
||||
locations->PopMany(parameter_types.size());
|
||||
|
||||
locations->Push(GetValueDefinition());
|
||||
}
|
||||
|
||||
DefinitionLocation MakeLazyNodeInstruction::GetValueDefinition() const {
|
||||
return DefinitionLocation::Instruction(this, 0);
|
||||
}
|
||||
|
||||
bool CallRuntimeInstruction::IsBlockTerminator() const {
|
||||
return is_tailcall || runtime_function->signature().return_type ==
|
||||
TypeOracle::GetNeverType();
|
||||
|
@ -49,6 +49,7 @@ class RuntimeFunction;
|
||||
V(ConstexprBranchInstruction) \
|
||||
V(GotoInstruction) \
|
||||
V(GotoExternalInstruction) \
|
||||
V(MakeLazyNodeInstruction) \
|
||||
V(ReturnInstruction) \
|
||||
V(PrintConstantStringInstruction) \
|
||||
V(AbortInstruction) \
|
||||
@ -457,6 +458,21 @@ struct CallCsaMacroAndBranchInstruction : InstructionBase {
|
||||
base::Optional<Block*> catch_block;
|
||||
};
|
||||
|
||||
struct MakeLazyNodeInstruction : InstructionBase {
|
||||
TORQUE_INSTRUCTION_BOILERPLATE()
|
||||
MakeLazyNodeInstruction(Macro* macro, const Type* result_type,
|
||||
std::vector<std::string> constexpr_arguments)
|
||||
: macro(macro),
|
||||
result_type(result_type),
|
||||
constexpr_arguments(std::move(constexpr_arguments)) {}
|
||||
|
||||
DefinitionLocation GetValueDefinition() const;
|
||||
|
||||
Macro* macro;
|
||||
const Type* result_type;
|
||||
std::vector<std::string> constexpr_arguments;
|
||||
};
|
||||
|
||||
struct CallBuiltinInstruction : InstructionBase {
|
||||
TORQUE_INSTRUCTION_BOILERPLATE()
|
||||
bool IsBlockTerminator() const override { return is_tailcall; }
|
||||
|
@ -114,6 +114,10 @@ class TypeOracle : public ContextualClass<TypeOracle> {
|
||||
return Declarations::LookupGlobalUniqueGenericType(SMI_TAGGED_TYPE_STRING);
|
||||
}
|
||||
|
||||
static GenericType* GetLazyGeneric() {
|
||||
return Declarations::LookupGlobalUniqueGenericType(LAZY_TYPE_STRING);
|
||||
}
|
||||
|
||||
static const Type* GetReferenceType(const Type* referenced_type,
|
||||
bool is_const) {
|
||||
return GetGenericTypeInstance(GetReferenceGeneric(is_const),
|
||||
|
@ -184,6 +184,23 @@ std::string Type::GetGeneratedTNodeTypeName() const {
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string AbstractType::GetGeneratedTypeNameImpl() const {
|
||||
// A special case that is not very well represented by the "generates"
|
||||
// syntax in the .tq files: Lazy<T> represents a std::function that
|
||||
// produces a TNode of the wrapped type.
|
||||
if (base::Optional<const Type*> type_wrapped_in_lazy =
|
||||
Type::MatchUnaryGeneric(this, TypeOracle::GetLazyGeneric())) {
|
||||
DCHECK(!IsConstexpr());
|
||||
return "std::function<" + (*type_wrapped_in_lazy)->GetGeneratedTypeName() +
|
||||
"()>";
|
||||
}
|
||||
|
||||
if (generated_type_.empty()) {
|
||||
return parent()->GetGeneratedTypeName();
|
||||
}
|
||||
return IsConstexpr() ? generated_type_ : "TNode<" + generated_type_ + ">";
|
||||
}
|
||||
|
||||
std::string AbstractType::GetGeneratedTNodeTypeNameImpl() const {
|
||||
if (generated_type_.empty()) return parent()->GetGeneratedTNodeTypeName();
|
||||
return generated_type_;
|
||||
|
@ -268,12 +268,7 @@ class AbstractType final : public Type {
|
||||
DECLARE_TYPE_BOILERPLATE(AbstractType)
|
||||
const std::string& name() const { return name_; }
|
||||
std::string ToExplicitString() const override { return name(); }
|
||||
std::string GetGeneratedTypeNameImpl() const override {
|
||||
if (generated_type_.empty()) {
|
||||
return parent()->GetGeneratedTypeName();
|
||||
}
|
||||
return IsConstexpr() ? generated_type_ : "TNode<" + generated_type_ + ">";
|
||||
}
|
||||
std::string GetGeneratedTypeNameImpl() const override;
|
||||
std::string GetGeneratedTNodeTypeNameImpl() const override;
|
||||
bool IsConstexpr() const final {
|
||||
const bool is_constexpr = flags_ & AbstractTypeFlag::kConstexpr;
|
||||
|
@ -898,6 +898,41 @@ TEST(TestCallMultiReturnBuiltin) {
|
||||
ft.Call();
|
||||
}
|
||||
|
||||
TEST(TestRunLazyTwice) {
|
||||
CcTest::InitializeVM();
|
||||
Isolate* isolate(CcTest::i_isolate());
|
||||
i::HandleScope scope(isolate);
|
||||
const int kNumParams = 0;
|
||||
int lazyNumber = 3;
|
||||
CodeAssemblerTester asm_tester(isolate, kNumParams);
|
||||
TestTorqueAssembler m(asm_tester.state());
|
||||
{
|
||||
CodeStubAssembler::LazyNode<Smi> lazy = [&]() {
|
||||
return m.SmiConstant(lazyNumber++);
|
||||
};
|
||||
m.Return(m.TestRunLazyTwice(lazy));
|
||||
}
|
||||
CHECK_EQ(lazyNumber, 5);
|
||||
FunctionTester ft(asm_tester.GenerateCode(), kNumParams);
|
||||
Handle<Object> result = ft.Call().ToHandleChecked();
|
||||
CHECK_EQ(7, Handle<Smi>::cast(result)->value());
|
||||
}
|
||||
|
||||
TEST(TestCreateLazyNodeFromTorque) {
|
||||
CcTest::InitializeVM();
|
||||
Isolate* isolate(CcTest::i_isolate());
|
||||
i::HandleScope scope(isolate);
|
||||
const int kNumParams = 0;
|
||||
CodeAssemblerTester asm_tester(isolate, kNumParams);
|
||||
TestTorqueAssembler m(asm_tester.state());
|
||||
{
|
||||
m.TestCreateLazyNodeFromTorque();
|
||||
m.Return(m.UndefinedConstant());
|
||||
}
|
||||
FunctionTester ft(asm_tester.GenerateCode(), kNumParams);
|
||||
ft.Call();
|
||||
}
|
||||
|
||||
} // namespace compiler
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -1367,4 +1367,47 @@ macro TestCallMultiReturnBuiltin(implicit context: Context)() {
|
||||
check(result.a == 445);
|
||||
check(result.b == FromConstexpr<String>('hi').map);
|
||||
}
|
||||
|
||||
@export
|
||||
macro TestRunLazyTwice(lazySmi: Lazy<Smi>): Smi {
|
||||
const firstResult = RunLazy(lazySmi);
|
||||
const secondResult = RunLazy(lazySmi);
|
||||
return firstResult + secondResult;
|
||||
}
|
||||
|
||||
macro GetLazySmi(): Smi {
|
||||
return 3;
|
||||
}
|
||||
|
||||
macro AddTwoSmiValues(a: Smi, b: Smi): Smi {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
macro AddSmiAndConstexprValues(a: Smi, b: constexpr int31): Smi {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
@export
|
||||
macro TestCreateLazyNodeFromTorque() {
|
||||
const lazy = %MakeLazy<Smi>('GetLazySmi');
|
||||
const result = TestRunLazyTwice(lazy);
|
||||
check(result == 6);
|
||||
|
||||
// The macro can also be referred to using namespace qualifications.
|
||||
const lazy2 = %MakeLazy<Smi>('test::GetLazySmi');
|
||||
const result2 = TestRunLazyTwice(lazy2);
|
||||
check(result2 == 6);
|
||||
|
||||
// We can save params to the macro. The most common usage is likely a
|
||||
// single-arg macro that just returns the arg, but we can use any number of
|
||||
// params.
|
||||
const lazy3 = %MakeLazy<Smi>('AddTwoSmiValues', 5, 6);
|
||||
const result3 = TestRunLazyTwice(lazy3);
|
||||
check(result3 == 22);
|
||||
|
||||
// It's okay if some of the params are constexpr and some aren't.
|
||||
const lazy4 = %MakeLazy<Smi>('AddSmiAndConstexprValues', 7, 8);
|
||||
const result4 = TestRunLazyTwice(lazy4);
|
||||
check(result4 == 30);
|
||||
}
|
||||
}
|
||||
|
@ -89,6 +89,7 @@ type SmiTagged<T : type extends uint31> extends Smi;
|
||||
type String extends HeapObject;
|
||||
type HeapNumber extends HeapObject;
|
||||
type FixedArrayBase extends HeapObject;
|
||||
type Lazy<T: type>;
|
||||
|
||||
struct float64_or_hole {
|
||||
is_hole: bool;
|
||||
|
Loading…
Reference in New Issue
Block a user