generate_kernels.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. import os
  4. import json
  5. import zlib
  6. from collections import defaultdict
  7. SPLIT_FILES = 20
  8. def is_strict(oid_per_name, funcs, name):
  9. found = None
  10. for oid in oid_per_name[name]:
  11. strict = funcs[oid][1]
  12. if found is None:
  13. found = strict
  14. else:
  15. assert found == strict
  16. return "true" if found else "false"
  17. def is_result_fixed(oid_per_name, catalog_by_oid, name):
  18. found = None
  19. for oid in oid_per_name[name]:
  20. fixed = catalog_by_oid[oid]["ret_type_fixed"]
  21. if found is None:
  22. found = fixed
  23. else:
  24. assert found == fixed
  25. return "true" if found else "false"
  26. def get_fixed_args(oid_per_name, catalog_by_oid, name):
  27. found = None
  28. for oid in oid_per_name[name]:
  29. if "var_type" in catalog_by_oid[oid]:
  30. return None
  31. fixed = [x["arg_type_fixed"] for x in catalog_by_oid[oid]["args"]]
  32. if found is None:
  33. found = fixed
  34. else:
  35. # e.g. range_constructor2
  36. if found != fixed:
  37. return None
  38. return found
  39. def main():
  40. pg_sources = []
  41. with open("pg_sources.inc") as f:
  42. for line in f:
  43. pg_sources.append(line.rstrip())
  44. with open("../../tools/pg_catalog_dump/dump.json") as f:
  45. catalog = json.load(f)
  46. catalog_by_oid = {}
  47. catalog_funcs = set()
  48. for proc in catalog["proc"]:
  49. catalog_by_oid[proc["oid"]] = proc
  50. catalog_funcs.add(proc["src"])
  51. catalog_aggs_by_id = {}
  52. for agg in catalog["aggregation"]:
  53. if not agg["combine_func_id"]:
  54. continue
  55. catalog_aggs_by_id[agg["agg_id"]] = agg
  56. assert len(agg["args"]) <= 2
  57. funcs={}
  58. with open("postgresql/src/backend/utils/fmgrtab.c") as f:
  59. parse=False
  60. for line in f:
  61. if "fmgr_builtins[]" in line:
  62. parse=True
  63. continue
  64. if not parse:
  65. continue
  66. if line.startswith("}"):
  67. parse=False
  68. continue
  69. c=line.strip()[1:-2].split(", ")
  70. oid=int(c[0])
  71. nargs=int(c[1])
  72. strict=c[2].strip()=="true"
  73. retset=c[3].strip()=="true"
  74. name=c[4].strip().strip('"')
  75. func=c[5].strip()
  76. if retset: continue
  77. if name!=func:
  78. print(name,func)
  79. continue
  80. if not oid in catalog_by_oid:
  81. print("skipped by catalog: ",name)
  82. continue
  83. funcs[oid] = (nargs, strict, name)
  84. print("funcs: ", len(funcs))
  85. func_names=set(x[2] for x in funcs.values())
  86. print("unique names: ", len(func_names))
  87. print("aggs: ", len(catalog_aggs_by_id))
  88. oid_per_name={}
  89. all_found_funcs=set()
  90. for x in funcs:
  91. name = funcs[x][2]
  92. if not name in oid_per_name:
  93. oid_per_name[name]=[]
  94. oid_per_name[name].append(x)
  95. symbols={}
  96. for i in range(len(pg_sources)):
  97. line = pg_sources[i]
  98. if not line.endswith(".c"):
  99. continue
  100. cfile = line.strip()
  101. found_funcs = set()
  102. #print(cfile)
  103. with open(cfile) as f:
  104. for srcline in f:
  105. pos=srcline.find("(PG_FUNCTION_ARGS)")
  106. if pos!=-1:
  107. names=[srcline[0:pos].strip()]
  108. elif srcline.startswith("CMPFUNC("):
  109. pos=srcline.find(",")
  110. names=[srcline[8:pos]]
  111. elif srcline.startswith("TSVECTORCMPFUNC("):
  112. pos=srcline.find(",")
  113. names=["tsvector_"+srcline[16:pos]]
  114. elif srcline.startswith("PSEUDOTYPE_DUMMY_IO_FUNCS("):
  115. pos=srcline.find(")")
  116. names=[srcline[26:pos]+"_in", srcline[26:pos]+"_out"]
  117. elif srcline.startswith("PSEUDOTYPE_DUMMY_INPUT_FUNC(") and "\\" not in srcline:
  118. pos=srcline.find(")")
  119. names=[srcline[28:pos]+"_in"]
  120. elif srcline.startswith("PSEUDOTYPE_DUMMY_BINARY_IO_FUNCS"):
  121. pos=srcline.find(")")
  122. names=[srcline[33:pos]+"_send", srcline[33:pos]+"_recv"]
  123. elif srcline.startswith("PSEUDOTYPE_DUMMY_RECEIVE_FUNC(") and "\\" not in srcline:
  124. pos=srcline.find(")")
  125. names=[srcline[30:pos]+"_recv"]
  126. elif srcline.startswith("PG_STAT_GET_DBENTRY_FLOAT8_MS("):
  127. pos=srcline.find(")")
  128. names=["pg_stat_get_db_" + srcline[30:pos]]
  129. elif srcline.startswith("PG_STAT_GET_DBENTRY_INT64("):
  130. pos=srcline.find(")")
  131. names=["pg_stat_get_db_" + srcline[26:pos]]
  132. elif srcline.startswith("PG_STAT_GET_RELENTRY_INT64("):
  133. pos=srcline.find(")")
  134. names=["pg_stat_get_" + srcline[27:pos]]
  135. elif srcline.startswith("PG_STAT_GET_RELENTRY_TIMESTAMPTZ("):
  136. pos=srcline.find(")")
  137. names=["pg_stat_get_" + srcline[33:pos]]
  138. elif srcline.startswith("PG_STAT_GET_XACT_RELENTRY_INT64("):
  139. pos=srcline.find(")")
  140. names=["pg_stat_get_xact_" + srcline[32:pos]]
  141. elif srcline.startswith("PG_STAT_GET_FUNCENTRY_FLOAT8_MS("):
  142. pos=srcline.find(")")
  143. names=["pg_stat_get_function_" + srcline[32:pos]]
  144. elif srcline.startswith("PG_STAT_GET_XACT_FUNCENTRY_FLOAT8_MS("):
  145. pos=srcline.find(")")
  146. names=["pg_stat_get_xact_function_" + srcline[37:pos]]
  147. else:
  148. continue
  149. for name in names:
  150. if name in func_names:
  151. found_funcs.add(name)
  152. all_found_funcs.add(name)
  153. if not found_funcs:
  154. continue
  155. print(cfile, len(found_funcs))
  156. symbols[cfile] = found_funcs
  157. split_symbols = []
  158. split_all_found_funcs = []
  159. for i in range(SPLIT_FILES):
  160. curr_symbols = {}
  161. curr_all_found_funcs = set()
  162. for cfile in symbols:
  163. if zlib.crc32(cfile.encode("utf8")) % SPLIT_FILES != i: continue
  164. curr_symbols[cfile] = symbols[cfile]
  165. curr_all_found_funcs.update(symbols[cfile])
  166. split_symbols.append(curr_symbols)
  167. split_all_found_funcs.append(curr_all_found_funcs)
  168. # check if all functions are available
  169. split_for_agg = {}
  170. for agg in catalog_aggs_by_id.values():
  171. oids = set()
  172. oids.add(agg["trans_func_id"])
  173. if agg["serialize_func_id"]:
  174. assert catalog_by_oid[agg["serialize_func_id"]]["strict"]
  175. oids.add(agg["serialize_func_id"])
  176. if agg["deserialize_func_id"]:
  177. assert catalog_by_oid[agg["deserialize_func_id"]]["strict"]
  178. oids.add(agg["deserialize_func_id"])
  179. if agg["final_func_id"]:
  180. oids.add(agg["final_func_id"])
  181. oids.add(agg["combine_func_id"])
  182. names = [catalog_by_oid[oid]["src"] for oid in oids]
  183. for i in range(SPLIT_FILES):
  184. with open("pg_bc."+str(i)+".inc", "w") as bc:
  185. bc.write("LLVM_BC(\n" + \
  186. "\n".join((" " + x) for x in sorted(split_symbols[i].keys())) + \
  187. "\n pg_kernels."+str(i)+".cpp\n" + \
  188. "\n NAME PgFuncs" + str(i) + "\n" + \
  189. "\n SYMBOLS\n" + \
  190. "\n".join((" arrow_" + x) for x in sorted(split_all_found_funcs[i])) + \
  191. "\n)\n")
  192. for i in range(SPLIT_FILES):
  193. with open("pg_proc_policies." + str(i) + ".inc", "w") as p:
  194. for x in sorted(split_all_found_funcs[i]):
  195. fixed_args = get_fixed_args(oid_per_name, catalog_by_oid, x)
  196. if fixed_args is not None:
  197. p.write("struct TArgs_NAME_Policy {\n".replace("NAME", x))
  198. p.write(" static constexpr bool VarArgs = false;\n")
  199. p.write(" static constexpr std::array<bool, N> IsFixedArg = {V};\n" \
  200. .replace("N", str(len(fixed_args))) \
  201. .replace("V", ",".join("true" if x else "false" for x in fixed_args)))
  202. p.write("};\n")
  203. else:
  204. print("polymorphic args:", x)
  205. for i in range(SPLIT_FILES):
  206. with open("pg_kernels." + str(i) + ".inc", "w") as k:
  207. for x in sorted(split_all_found_funcs[i]):
  208. fixed_args = get_fixed_args(oid_per_name, catalog_by_oid, x)
  209. k.write(
  210. "TExecFunc arrow_NAME() { return TGenericExec<TPgDirectFunc<&NAME>, STRICT, IS_RESULT_FIXED, POLICY>({}); }\n" \
  211. .replace("NAME", x) \
  212. .replace("STRICT", is_strict(oid_per_name, funcs, x)) \
  213. .replace("IS_RESULT_FIXED", is_result_fixed(oid_per_name, catalog_by_oid, x))
  214. .replace("POLICY", "TArgs_" + x + "_Policy" if fixed_args is not None else "TDefaultArgsPolicy"))
  215. for i in range(SPLIT_FILES):
  216. with open("pg_kernels.slow." + str(i) + ".inc", "w") as k:
  217. for x in sorted(split_all_found_funcs[i]):
  218. k.write(
  219. "TExecFunc arrow_NAME() { return MakeIndirectExec<STRICT, IS_RESULT_FIXED>(&NAME); }\n" \
  220. .replace("NAME", x) \
  221. .replace("STRICT", is_strict(oid_per_name, funcs, x)) \
  222. .replace("IS_RESULT_FIXED", is_result_fixed(oid_per_name, catalog_by_oid, x)))
  223. with open("pg_kernels_fwd.inc", "w") as k:
  224. k.write(\
  225. "\n".join("extern TExecFunc arrow_NAME();".replace("NAME", x) for x in sorted(all_found_funcs)) + \
  226. "\n")
  227. for i in range(SPLIT_FILES):
  228. with open("pg_kernels_register." + str(i) + ".inc", "w") as r:
  229. for name in oid_per_name:
  230. if not name in split_all_found_funcs[i]: continue
  231. for oid in sorted(oid_per_name[name]):
  232. r.write("RegisterExec(" + str(oid) + ", arrow_" + name + "());\n")
  233. for slow in [False, True]:
  234. with open("pg_aggs" + (".slow" if slow else "") + ".inc","w") as p:
  235. for agg_id in sorted(catalog_aggs_by_id.keys()):
  236. agg = catalog_aggs_by_id[agg_id]
  237. trans_func = catalog_by_oid[agg["trans_func_id"]]["src"]
  238. trans_fixed_args = None if slow else get_fixed_args(oid_per_name, catalog_by_oid, trans_func)
  239. combine_func = catalog_by_oid[agg["combine_func_id"]]["src"]
  240. combine_fixed_args = None if slow else get_fixed_args(oid_per_name, catalog_by_oid, combine_func)
  241. serialize_func = ""
  242. serialize_fixed_args = None
  243. if agg["serialize_func_id"]:
  244. serialize_func = catalog_by_oid[agg["serialize_func_id"]]["src"]
  245. serialize_fixed_args = None if slow else get_fixed_args(oid_per_name, catalog_by_oid, serialize_func)
  246. deserialize_func = ""
  247. deserialize_fixed_args = None
  248. if agg["deserialize_func_id"]:
  249. deserialize_func = catalog_by_oid[agg["deserialize_func_id"]]["src"]
  250. deserialize_fixed_args = None if slow else get_fixed_args(oid_per_name, catalog_by_oid, deserialize_func)
  251. final_func = ""
  252. final_fixed_args = None
  253. if agg["final_func_id"]:
  254. final_func = catalog_by_oid[agg["final_func_id"]]["src"]
  255. final_fixed_args = None if slow else get_fixed_args(oid_per_name, catalog_by_oid, final_func)
  256. p.write("auto MakePgAgg_" + agg["name"] + "_" + str(agg_id) + "() {\n"
  257. " return TGenericAgg<\n \
  258. TRANS_FUNC, IS_TRANS_STRICT, TRANS_ARGS_POLICY,\n \
  259. COMBINE_FUNC, IS_COMBINE_STRICT, COMBINE_ARGS_POLICY,\n \
  260. HAS_SERIALIZE_FUNC, SERIALIZE_FUNC1, SERIALIZE_ARGS_POLICY1,\n \
  261. HAS_DESERIALIZE_FUNC, DESERIALIZE_FUNC, DESERIALIZE_ARGS_POLICY,\n \
  262. HAS_FINAL_FUNC, FINAL_FUNC, IS_FINAL_STRICT, FINAL_ARGS_POLICY,\n \
  263. TRANS_TYPE_FIXED, SERIALIZED_TYPE_FIXED, FINAL_TYPE_FIXED, HAS_INIT_VALUE\n \
  264. >(TRANS_OBJ, COMBINE_OBJ, SERIALIZE1_OBJ, DESERIALIZE_OBJ, FINAL_OBJ);\n" \
  265. .replace("TRANS_FUNC", "TPgIndirectFunc" if slow else "TPgDirectFunc<&" + trans_func + ">") \
  266. .replace("IS_TRANS_STRICT", "true" if catalog_by_oid[agg["trans_func_id"]]["strict"] else "false") \
  267. .replace("TRANS_ARGS_POLICY", "TArgs_" + trans_func + "_Policy" if trans_fixed_args is not None else "TDefaultArgsPolicy") \
  268. .replace("COMBINE_FUNC", "TPgIndirectFunc" if slow else "TPgDirectFunc<&" + combine_func + ">") \
  269. .replace("IS_COMBINE_STRICT", "true" if catalog_by_oid[agg["combine_func_id"]]["strict"] else "false") \
  270. .replace("COMBINE_ARGS_POLICY", "TArgs_" + combine_func + "_Policy" if combine_fixed_args is not None else "TDefaultArgsPolicy") \
  271. .replace("HAS_SERIALIZE_FUNC", "true" if serialize_func else "false") \
  272. .replace("SERIALIZE_FUNC1", "TPgDirectFunc<&" + serialize_func + ">" if serialize_func and not slow else "TPgIndirectFunc") \
  273. .replace("SERIALIZE_ARGS_POLICY1", "TArgs_" + serialize_func + "_Policy" if serialize_fixed_args is not None else "TDefaultArgsPolicy") \
  274. .replace("HAS_DESERIALIZE_FUNC", "true" if deserialize_func else "false") \
  275. .replace("DESERIALIZE_FUNC", "TPgDirectFunc<&" + deserialize_func + ">" if deserialize_func and not slow else "TPgIndirectFunc") \
  276. .replace("DESERIALIZE_ARGS_POLICY", "TArgs_" + deserialize_func + "_Policy" if deserialize_fixed_args is not None else "TDefaultArgsPolicy") \
  277. .replace("HAS_FINAL_FUNC", "true" if final_func else "false") \
  278. .replace("FINAL_FUNC", "TPgDirectFunc<&" + final_func + ">" if final_func and not slow else "TPgIndirectFunc") \
  279. .replace("IS_FINAL_STRICT", "true" if final_func and catalog_by_oid[agg["final_func_id"]]["strict"] else "false") \
  280. .replace("FINAL_ARGS_POLICY", "TArgs_" + final_func + "_Policy" if final_fixed_args is not None else "TDefaultArgsPolicy") \
  281. .replace("TRANS_TYPE_FIXED", "true" if agg["trans_type_fixed"] else "false") \
  282. .replace("SERIALIZED_TYPE_FIXED", "true" if agg["serialized_type_fixed"] else "false") \
  283. .replace("FINAL_TYPE_FIXED", "true" if agg["ret_type_fixed"] else "false") \
  284. .replace("HAS_INIT_VALUE", "true" if agg["has_init_value"] else "false") \
  285. .replace("TRANS_OBJ", "&" + trans_func if slow else "{}") \
  286. .replace("COMBINE_OBJ", "&" + combine_func if slow else "{}") \
  287. .replace("SERIALIZE1_OBJ", ("&" + serialize_func if slow else "{}") if serialize_func else "nullptr") \
  288. .replace("DESERIALIZE_OBJ", ("&" +deserialize_func if slow else "{}") if deserialize_func else "nullptr") \
  289. .replace("FINAL_OBJ", ("&" + final_func if slow else "{}") if final_func else "nullptr") \
  290. )
  291. p.write("}\n")
  292. agg_names = defaultdict(list)
  293. with open("pg_aggs_register.inc","w") as p:
  294. for agg_id in sorted(catalog_aggs_by_id.keys()):
  295. agg_names[catalog_aggs_by_id[agg_id]["name"]].append(agg_id)
  296. for name in agg_names:
  297. p.write(
  298. ("class TPgAggFactory_NAME: public IBlockAggregatorFactory {\n" \
  299. "std::unique_ptr<IPreparedBlockAggregator<IBlockAggregatorCombineAll>> PrepareCombineAll(\n" \
  300. " TTupleType* tupleType,\n" \
  301. " std::optional<ui32> filterColumn,\n" \
  302. " const std::vector<ui32>& argsColumns,\n" \
  303. " const TTypeEnvironment& env) const final {\n" \
  304. " const auto& aggDesc = ResolveAggregation(\"NAME\", tupleType, argsColumns, nullptr);\n" \
  305. " switch (aggDesc.AggId) {\n" +
  306. "".join([" case " + str(agg_id) + ": return MakePgAgg_NAME_" + str(agg_id) + "().PrepareCombineAll(filterColumn, argsColumns, aggDesc);\n" for agg_id in agg_names[name]]) +
  307. " default: throw yexception() << \"Unsupported agg id: \" << aggDesc.AggId;\n" \
  308. " }\n" \
  309. "}\n" \
  310. "\n" \
  311. "std::unique_ptr<IPreparedBlockAggregator<IBlockAggregatorCombineKeys>> PrepareCombineKeys(\n" \
  312. " TTupleType* tupleType,\n" \
  313. " const std::vector<ui32>& argsColumns,\n" \
  314. " const TTypeEnvironment& env) const final {\n" \
  315. " const auto& aggDesc = ResolveAggregation(\"NAME\", tupleType, argsColumns, nullptr);\n"
  316. " switch (aggDesc.AggId) {\n" +
  317. "".join([" case " + str(agg_id) + ": return MakePgAgg_NAME_" + str(agg_id) + "().PrepareCombineKeys(argsColumns, aggDesc);\n" for agg_id in agg_names[name]]) +
  318. " default: throw yexception() << \"Unsupported agg id: \" << aggDesc.AggId;\n" \
  319. " }\n" \
  320. "}\n" \
  321. "\n" \
  322. "std::unique_ptr<IPreparedBlockAggregator<IBlockAggregatorFinalizeKeys>> PrepareFinalizeKeys(\n" \
  323. " TTupleType* tupleType,\n" \
  324. " const std::vector<ui32>& argsColumns,\n" \
  325. " const TTypeEnvironment& env,\n" \
  326. " TType* returnType,\n" \
  327. " ui32 hint) const final {\n" \
  328. " const auto& aggDesc = ResolveAggregation(\"NAME\", tupleType, argsColumns, returnType, hint);\n"
  329. " switch (aggDesc.AggId) {\n" +
  330. "".join([" case " + str(agg_id) + ": return MakePgAgg_NAME_" + str(agg_id) + "().PrepareFinalizeKeys(argsColumns.front(), aggDesc);\n" for agg_id in agg_names[name]]) +
  331. " default: throw yexception() << \"Unsupported agg id: \" << aggDesc.AggId;\n" \
  332. " }\n" \
  333. "}\n" \
  334. "};\n").replace("NAME", name))
  335. for name in agg_names:
  336. p.write('registry.emplace("pg_' + name + '", std::make_unique<TPgAggFactory_' + name + '>());\n')
  337. for i in range(SPLIT_FILES):
  338. with open("pg_kernels." + str(i) + ".cpp","w") as f:
  339. f.write(
  340. 'extern "C" {\n'
  341. '#include "postgres.h"\n'
  342. '#include "fmgr.h"\n'
  343. '#include "postgresql/src/backend/utils/fmgrprotos.h"\n'
  344. '#undef Abs\n'
  345. '#undef Min\n'
  346. '#undef Max\n'
  347. '#undef TypeName\n'
  348. '#undef SortBy\n'
  349. '#undef Sort\n'
  350. '#undef Unique\n'
  351. '#undef LOG\n'
  352. '#undef INFO\n'
  353. '#undef NOTICE\n'
  354. '#undef WARNING\n'
  355. '#undef ERROR\n'
  356. '#undef FATAL\n'
  357. '#undef PANIC\n'
  358. '#undef open\n'
  359. '#undef fopen\n'
  360. '#undef bind\n'
  361. '#undef locale_t\n'
  362. '#undef strtou64\n'
  363. '}\n'
  364. '\n'
  365. '#include "arrow.h"\n'
  366. '\n'
  367. 'namespace NYql {\n'
  368. '\n'
  369. 'extern "C" {\n'
  370. '\n'
  371. 'Y_PRAGMA_DIAGNOSTIC_PUSH\n'
  372. 'Y_PRAGMA("GCC diagnostic ignored \\"-Wreturn-type-c-linkage\\"")\n'
  373. '#ifdef USE_SLOW_PG_KERNELS\n'
  374. '#include "pg_kernels.slow.INDEX.inc"\n'
  375. '#else\n'
  376. '#include "pg_proc_policies.INDEX.inc"\n'
  377. '#include "pg_kernels.INDEX.inc"\n'
  378. '#endif\n'
  379. 'Y_PRAGMA_DIAGNOSTIC_POP\n'
  380. '\n'
  381. '}\n'
  382. '\n'
  383. '}\n'.replace("INDEX",str(i))
  384. )
  385. with open("pg_kernels_register.all.inc","w") as f:
  386. for i in range(SPLIT_FILES):
  387. f.write('#include "pg_kernels_register.INDEX.inc"\n'.replace("INDEX", str(i)))
  388. with open("pg_proc_policies.all.inc","w") as f:
  389. for i in range(SPLIT_FILES):
  390. f.write('#include "pg_proc_policies.INDEX.inc"\n'.replace("INDEX", str(i)))
  391. with open("pg_kernel_sources.inc","w") as f:
  392. f.write("SRCS(\n")
  393. for i in range(SPLIT_FILES):
  394. f.write(' pg_kernels.INDEX.cpp\n'.replace("INDEX", str(i)))
  395. f.write(")\n")
  396. with open("pg_bc.all.inc","w") as f:
  397. for i in range(SPLIT_FILES):
  398. f.write('INCLUDE(pg_bc.INDEX.inc)\n'.replace("INDEX", str(i)))
  399. print("found funcs: ",len(all_found_funcs))
  400. print("agg names: ",len(agg_names))
  401. print("agg funcs: ",len(catalog_aggs_by_id))
  402. missing=func_names.difference(all_found_funcs)
  403. if missing:
  404. print("missing funcs: ",len(missing))
  405. print(missing)
  406. if __name__ == "__main__":
  407. main()