1#!/usr/bin/env python3 2 3# Copyright 2023 gRPC authors. 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17import sys 18 19from mako.template import Template 20 21seq_state = Template( 22 """ 23<%def name="decl(promise_name, i, n)"> 24using Promise${i} = ${promise_name}; 25% if i < n-1: 26using NextFactory${i} = OncePromiseFactory<typename Promise${i}::Result, F${i}>; 27${decl(f"typename NextFactory{i}::Promise", i+1, n)} 28% endif 29</%def> 30 31<%def name="state(i, n)"> 32% if i == 0: 33Promise0 current_promise; 34NextFactory0 next_factory; 35% elif i == n-1: 36union { 37 struct { ${state(i-1, n)} } prior; 38 Promise${i} current_promise; 39}; 40% else: 41union { 42 struct { ${state(i-1, n)} } prior; 43 P${i} current_promise; 44}; 45NextFactory${i} next_factory; 46% endif 47</%def> 48 49template <template<typename> class Traits, typename P, ${",".join(f"typename F{i}" for i in range(0,n-1))}> 50struct SeqState<Traits, P, ${",".join(f"F{i}" for i in range(0,n-1))}> { 51<% name="PromiseLike<P>" %> 52% for i in range(0,n-1): 53using Promise${i} = ${name}; 54using PromiseResult${i} = typename Promise${i}::Result; 55using PromiseResultTraits${i} = Traits<PromiseResult${i}>; 56using NextFactory${i} = OncePromiseFactory<typename PromiseResultTraits${i}::UnwrappedType, F${i}>; 57<% name=f"typename NextFactory{i}::Promise" %>\\ 58% endfor 59using Promise${n-1} = ${name}; 60using PromiseResult${n-1} = typename Promise${n-1}::Result; 61using PromiseResultTraits${n-1} = Traits<PromiseResult${n-1}>; 62using Result = typename PromiseResultTraits${n-1}::WrappedType; 63% if n == 1: 64Promise0 current_promise; 65% else: 66% for i in range(0,n-1): 67struct Running${i} { 68% if i != 0: 69union { 70 GPR_NO_UNIQUE_ADDRESS Running${i-1} prior; 71% endif 72 GPR_NO_UNIQUE_ADDRESS Promise${i} current_promise; 73% if i != 0: 74}; 75% endif 76GPR_NO_UNIQUE_ADDRESS NextFactory${i} next_factory; 77}; 78% endfor 79union { 80 GPR_NO_UNIQUE_ADDRESS Running${n-2} prior; 81 GPR_NO_UNIQUE_ADDRESS Promise${n-1} current_promise; 82}; 83% endif 84 enum class State : uint8_t { ${",".join(f"kState{i}" for i in range(0,n))} }; 85 GPR_NO_UNIQUE_ADDRESS State state = State::kState0; 86 GPR_NO_UNIQUE_ADDRESS DebugLocation whence; 87 88 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION SeqState(P&& p, 89 ${",".join(f"F{i}&& f{i}" for i in range(0,n-1))}, 90 DebugLocation whence) noexcept: whence(whence) { 91 Construct(&${"prior."*(n-1)}current_promise, std::forward<P>(p)); 92% for i in range(0,n-1): 93 Construct(&${"prior."*(n-1-i)}next_factory, std::forward<F${i}>(f${i})); 94% endfor 95 } 96 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION ~SeqState() { 97 switch (state) { 98% for i in range(0,n-1): 99 case State::kState${i}: 100 Destruct(&${"prior."*(n-1-i)}current_promise); 101 goto tail${i}; 102% endfor 103 case State::kState${n-1}: 104 Destruct(¤t_promise); 105 return; 106 } 107% for i in range(0,n-1): 108tail${i}: 109 Destruct(&${"prior."*(n-1-i)}next_factory); 110% endfor 111 } 112 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION SeqState(const SeqState& other) noexcept : state(other.state), whence(other.whence) { 113 DCHECK(state == State::kState0); 114 Construct(&${"prior."*(n-1)}current_promise, 115 other.${"prior."*(n-1)}current_promise); 116% for i in range(0,n-1): 117 Construct(&${"prior."*(n-1-i)}next_factory, 118 other.${"prior."*(n-1-i)}next_factory); 119% endfor 120 } 121 SeqState& operator=(const SeqState& other) = delete; 122 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION SeqState(SeqState&& other) noexcept : state(other.state), whence(other.whence) { 123 DCHECK(state == State::kState0); 124 Construct(&${"prior."*(n-1)}current_promise, 125 std::move(other.${"prior."*(n-1)}current_promise)); 126% for i in range(0,n-1): 127 Construct(&${"prior."*(n-1-i)}next_factory, 128 std::move(other.${"prior."*(n-1-i)}next_factory)); 129% endfor 130 } 131 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION SeqState& operator=(SeqState&& other) = delete; 132 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION Poll<Result> PollOnce() { 133 switch (state) { 134% for i in range(0,n-1): 135 case State::kState${i}: { 136 GRPC_TRACE_LOG(promise_primitives, INFO).AtLocation(whence.file(), whence.line()) 137 << "seq[" << this << "]: begin poll step ${i+1}/${n}"; 138 auto result = ${"prior."*(n-1-i)}current_promise(); 139 PromiseResult${i}* p = result.value_if_ready(); 140 GRPC_TRACE_LOG(promise_primitives, INFO).AtLocation(whence.file(), whence.line()) 141 << "seq[" << this << "]: poll step ${i+1}/${n} gets " 142 << (p != nullptr 143 ? (PromiseResultTraits${i}::IsOk(*p) 144 ? "ready" 145 : absl::StrCat("early-error:", PromiseResultTraits${i}::ErrorString(*p)).c_str()) 146 : "pending"); 147 if (p == nullptr) return Pending{}; 148 if (!PromiseResultTraits${i}::IsOk(*p)) { 149 return PromiseResultTraits${i}::template ReturnValue<Result>(std::move(*p)); 150 } 151 Destruct(&${"prior."*(n-1-i)}current_promise); 152 auto next_promise = PromiseResultTraits${i}::CallFactory(&${"prior."*(n-1-i)}next_factory, std::move(*p)); 153 Destruct(&${"prior."*(n-1-i)}next_factory); 154 Construct(&${"prior."*(n-2-i)}current_promise, std::move(next_promise)); 155 state = State::kState${i+1}; 156 } 157 ABSL_FALLTHROUGH_INTENDED; 158% endfor 159 default: 160 case State::kState${n-1}: { 161 GRPC_TRACE_LOG(promise_primitives, INFO).AtLocation(whence.file(), whence.line()) 162 << "seq[" << this << "]: begin poll step ${n}/${n}"; 163 auto result = current_promise(); 164 GRPC_TRACE_LOG(promise_primitives, INFO).AtLocation(whence.file(), whence.line()) 165 << "seq[" << this << "]: poll step ${n}/${n} gets " 166 << (result.ready()? "ready" : "pending"); 167 auto* p = result.value_if_ready(); 168 if (p == nullptr) return Pending{}; 169 return Result(std::move(*p)); 170 } 171 } 172 } 173};""" 174) 175 176front_matter = """ 177#ifndef GRPC_SRC_CORE_LIB_PROMISE_DETAIL_SEQ_STATE_H 178#define GRPC_SRC_CORE_LIB_PROMISE_DETAIL_SEQ_STATE_H 179 180// This file is generated by tools/codegen/core/gen_seq.py 181 182#include <grpc/support/port_platform.h> 183 184#include <stdint.h> 185 186#include <utility> 187 188#include "absl/log/check.h" 189#include "absl/log/log.h" 190#include "absl/base/attributes.h" 191#include "absl/strings/str_cat.h" 192 193#include "src/core/lib/debug/trace.h" 194#include "src/core/util/construct_destruct.h" 195#include "src/core/util/debug_location.h" 196#include "src/core/lib/promise/detail/promise_factory.h" 197#include "src/core/lib/promise/detail/promise_like.h" 198#include "src/core/lib/promise/poll.h" 199 200// A sequence under some traits for some set of callables P, Fs. 201// P should be a promise-like object that yields a value. 202// Fs... should be promise-factory-like objects that take the value from the 203// previous step and yield a promise. Note that most of the machinery in 204// PromiseFactory exists to make it possible for those promise-factory-like 205// objects to be anything that's convenient. 206// Traits defines how we move from one step to the next. Traits sets up the 207// wrapping and escape handling for the sequence. 208// Promises return wrapped values that the trait can inspect and unwrap before 209// passing them to the next element of the sequence. The trait can 210// also interpret a wrapped value as an escape value, which terminates 211// evaluation of the sequence immediately yielding a result. Traits for type T 212// have the members: 213// * type UnwrappedType - the type after removing wrapping from T (i.e. for 214// TrySeq, T=StatusOr<U> yields UnwrappedType=U). 215// * type WrappedType - the type after adding wrapping if it doesn't already 216// exist (i.e. for TrySeq if T is not Status/StatusOr/void, then 217// WrappedType=StatusOr<T>; if T is Status then WrappedType=Status (it's 218// already wrapped!)) 219// * template <typename Next> void CallFactory(Next* next_factory, T&& value) - 220// call promise factory next_factory with the result of unwrapping value, and 221// return the resulting promise. 222// * template <typename Result, typename RunNext> Poll<Result> 223// CheckResultAndRunNext(T prior, RunNext run_next) - examine the value of 224// prior, and decide to escape or continue. If escaping, return the final 225// sequence value of type Poll<Result>. If continuing, return the value of 226// run_next(std::move(prior)). 227// 228// A state contains the current promise, and the promise factory to turn the 229// result of the current promise into the next state's promise. We play a shell 230// game such that the prior state and our current promise are kept in a union, 231// and the next promise factory is kept alongside in the state struct. 232// Recursively this guarantees that the next functions get initialized once, and 233// destroyed once, and don't need to be moved around in between, which avoids a 234// potential O(n**2) loop of next factory moves had we used a variant of states 235// here. The very first state does not have a prior state, and so that state has 236// a partial specialization below. The final state does not have a next state; 237// that state is inlined in BasicSeq since that was simpler to type. 238 239namespace grpc_core { 240namespace promise_detail { 241template <template<typename> class Traits, typename P, typename... Fs> 242struct SeqState; 243""" 244 245end_matter = """ 246} // namespace promise_detail 247} // namespace grpc_core 248 249#endif // GRPC_SRC_CORE_LIB_PROMISE_DETAIL_SEQ_STATE_H 250""" 251 252 253# utility: print a big comment block into a set of files 254def put_banner(files, banner): 255 for f in files: 256 for line in banner: 257 print("// %s" % line, file=f) 258 print("", file=f) 259 260 261with open(sys.argv[0]) as my_source: 262 copyright = [] 263 for line in my_source: 264 if line[0] != "#": 265 break 266 for line in my_source: 267 if line[0] == "#": 268 copyright.append(line) 269 break 270 for line in my_source: 271 if line[0] != "#": 272 break 273 copyright.append(line) 274 275copyright = [line[2:].rstrip() for line in copyright] 276 277with open("src/core/lib/promise/detail/seq_state.h", "w") as f: 278 put_banner([f], copyright) 279 print(front_matter, file=f) 280 for n in range(2, 14): 281 print(seq_state.render(n=n), file=f) 282 print(end_matter, file=f) 283