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 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 ~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 SeqState(const SeqState& other) noexcept : state(other.state), whence(other.whence) { 113 GPR_ASSERT(state == State::kState0); 114 Construct(&${"prior."*(n-1-i)}current_promise, 115 other.${"prior."*(n-1-i)}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 SeqState(SeqState&& other) noexcept : state(other.state), whence(other.whence) { 123 switch (state) { 124% for i in range(0,n-1): 125 case State::kState${i}: 126 Construct(&${"prior."*(n-1-i)}current_promise, 127 std::move(other.${"prior."*(n-1-i)}current_promise)); 128 goto tail${i}; 129% endfor 130 case State::kState${n-1}: 131 Construct(¤t_promise, std::move(other.current_promise)); 132 return; 133 } 134% for i in range(0,n-1): 135tail${i}: 136 Construct(&${"prior."*(n-1-i)}next_factory, 137 std::move(other.${"prior."*(n-1-i)}next_factory)); 138% endfor 139 } 140 SeqState& operator=(SeqState&& other) = delete; 141 Poll<Result> PollOnce() { 142 switch (state) { 143% for i in range(0,n-1): 144 case State::kState${i}: { 145 if (grpc_trace_promise_primitives.enabled()) { 146 gpr_log(whence.file(), whence.line(), GPR_LOG_SEVERITY_DEBUG, "seq[%p]: begin poll step ${i+1}/${n}", this); 147 } 148 auto result = ${"prior."*(n-1-i)}current_promise(); 149 PromiseResult${i}* p = result.value_if_ready(); 150 if (grpc_trace_promise_primitives.enabled()) { 151 gpr_log(whence.file(), whence.line(), GPR_LOG_SEVERITY_DEBUG, "seq[%p]: poll step ${i+1}/${n} gets %s", this, 152 p != nullptr 153 ? (PromiseResultTraits${i}::IsOk(*p) 154 ? "ready" 155 : absl::StrCat("early-error:", PromiseResultTraits${i}::ErrorString(*p)).c_str()) 156 : "pending"); 157 } 158 if (p == nullptr) return Pending{}; 159 if (!PromiseResultTraits${i}::IsOk(*p)) { 160 return PromiseResultTraits${i}::template ReturnValue<Result>(std::move(*p)); 161 } 162 Destruct(&${"prior."*(n-1-i)}current_promise); 163 auto next_promise = PromiseResultTraits${i}::CallFactory(&${"prior."*(n-1-i)}next_factory, std::move(*p)); 164 Destruct(&${"prior."*(n-1-i)}next_factory); 165 Construct(&${"prior."*(n-2-i)}current_promise, std::move(next_promise)); 166 state = State::kState${i+1}; 167 } 168 ABSL_FALLTHROUGH_INTENDED; 169% endfor 170 default: 171 case State::kState${n-1}: { 172 if (grpc_trace_promise_primitives.enabled()) { 173 gpr_log(whence.file(), whence.line(), GPR_LOG_SEVERITY_DEBUG, "seq[%p]: begin poll step ${n}/${n}", this); 174 } 175 auto result = current_promise(); 176 if (grpc_trace_promise_primitives.enabled()) { 177 gpr_log(whence.file(), whence.line(), GPR_LOG_SEVERITY_DEBUG, "seq[%p]: poll step ${n}/${n} gets %s", this, result.ready()? "ready" : "pending"); 178 } 179 auto* p = result.value_if_ready(); 180 if (p == nullptr) return Pending{}; 181 return Result(std::move(*p)); 182 } 183 } 184 } 185};""" 186) 187 188front_matter = """ 189#ifndef GRPC_SRC_CORE_LIB_PROMISE_DETAIL_SEQ_STATE_H 190#define GRPC_SRC_CORE_LIB_PROMISE_DETAIL_SEQ_STATE_H 191 192// This file is generated by tools/codegen/core/gen_seq.py 193 194#include <grpc/support/port_platform.h> 195 196#include <stdint.h> 197 198#include <utility> 199 200#include "absl/base/attributes.h" 201#include "absl/strings/str_cat.h" 202 203#include <grpc/support/log.h> 204 205#include "src/core/lib/gprpp/construct_destruct.h" 206#include "src/core/lib/gprpp/debug_location.h" 207#include "src/core/lib/promise/detail/promise_factory.h" 208#include "src/core/lib/promise/detail/promise_like.h" 209#include "src/core/lib/promise/poll.h" 210#include "src/core/lib/promise/trace.h" 211 212// A sequence under some traits for some set of callables P, Fs. 213// P should be a promise-like object that yields a value. 214// Fs... should be promise-factory-like objects that take the value from the 215// previous step and yield a promise. Note that most of the machinery in 216// PromiseFactory exists to make it possible for those promise-factory-like 217// objects to be anything that's convenient. 218// Traits defines how we move from one step to the next. Traits sets up the 219// wrapping and escape handling for the sequence. 220// Promises return wrapped values that the trait can inspect and unwrap before 221// passing them to the next element of the sequence. The trait can 222// also interpret a wrapped value as an escape value, which terminates 223// evaluation of the sequence immediately yielding a result. Traits for type T 224// have the members: 225// * type UnwrappedType - the type after removing wrapping from T (i.e. for 226// TrySeq, T=StatusOr<U> yields UnwrappedType=U). 227// * type WrappedType - the type after adding wrapping if it doesn't already 228// exist (i.e. for TrySeq if T is not Status/StatusOr/void, then 229// WrappedType=StatusOr<T>; if T is Status then WrappedType=Status (it's 230// already wrapped!)) 231// * template <typename Next> void CallFactory(Next* next_factory, T&& value) - 232// call promise factory next_factory with the result of unwrapping value, and 233// return the resulting promise. 234// * template <typename Result, typename RunNext> Poll<Result> 235// CheckResultAndRunNext(T prior, RunNext run_next) - examine the value of 236// prior, and decide to escape or continue. If escaping, return the final 237// sequence value of type Poll<Result>. If continuing, return the value of 238// run_next(std::move(prior)). 239// 240// A state contains the current promise, and the promise factory to turn the 241// result of the current promise into the next state's promise. We play a shell 242// game such that the prior state and our current promise are kept in a union, 243// and the next promise factory is kept alongside in the state struct. 244// Recursively this guarantees that the next functions get initialized once, and 245// destroyed once, and don't need to be moved around in between, which avoids a 246// potential O(n**2) loop of next factory moves had we used a variant of states 247// here. The very first state does not have a prior state, and so that state has 248// a partial specialization below. The final state does not have a next state; 249// that state is inlined in BasicSeq since that was simpler to type. 250 251namespace grpc_core { 252namespace promise_detail { 253template <template<typename> class Traits, typename P, typename... Fs> 254struct SeqState; 255""" 256 257end_matter = """ 258} // namespace promise_detail 259} // namespace grpc_core 260 261#endif // GRPC_SRC_CORE_LIB_PROMISE_DETAIL_SEQ_STATE_H 262""" 263 264 265# utility: print a big comment block into a set of files 266def put_banner(files, banner): 267 for f in files: 268 for line in banner: 269 print("// %s" % line, file=f) 270 print("", file=f) 271 272 273with open(sys.argv[0]) as my_source: 274 copyright = [] 275 for line in my_source: 276 if line[0] != "#": 277 break 278 for line in my_source: 279 if line[0] == "#": 280 copyright.append(line) 281 break 282 for line in my_source: 283 if line[0] != "#": 284 break 285 copyright.append(line) 286 287copyright = [line[2:].rstrip() for line in copyright] 288 289with open("src/core/lib/promise/detail/seq_state.h", "w") as f: 290 put_banner([f], copyright) 291 print(front_matter, file=f) 292 for n in range(2, 14): 293 print(seq_state.render(n=n), file=f) 294 print(end_matter, file=f) 295