diff --git a/.github/workflows/plugin.yml b/.github/workflows/plugin.yml index 90d9dec..d63995e 100644 --- a/.github/workflows/plugin.yml +++ b/.github/workflows/plugin.yml @@ -151,6 +151,15 @@ jobs: shell: bash run: make -C gxplug check GCC="$GXPLUG_GCC" + # The five plugins B05 is written around, built and loaded on this channel too. The + # lesson shows a recording made on one machine, so this is what stops it describing + # plugins that only work where they were written. It also covers the three things + # gxplug itself never exercises: a pass inserted into the pipeline, a gate overridden + # from outside, and a plugin being refused at load time on purpose. + - name: the five example plugins do what B05 says they do + shell: bash + run: make -C gxplug examples-check GCC="$GXPLUG_GCC" + # A build that produces no events is a plugin that loaded and did nothing, which the # byte comparison above would happily call a pass. - name: the stream is real, and reaches RTL diff --git a/GLOSSARY.md b/GLOSSARY.md index 9dd8a08..a5c6ce9 100644 --- a/GLOSSARY.md +++ b/GLOSSARY.md @@ -8,7 +8,7 @@ This file is generated from `gxray/glossary.py`. Edit that and run `just build-g ## Index -[DejaGnu](#dejagnu) | [GENERIC](#generic) | [GIMPLE](#gimple) | [IRA](#ira) | [LRA](#lra) | [RTL](#rtl) | [RTX](#rtx) | [SSA](#ssa) | [SSA name](#ssa-name) | [allocno](#allocno) | [alternative](#alternative) | [assembler directive](#assembler-directive) | [back end](#back-end) | [basic block](#basic-block) | [bootstrap](#bootstrap) | [bubbling](#bubbling) | [build config](#build-config) | [cc1](#cc1) | [checking build](#checking-build) | [collect2](#collect2) | [constraint](#constraint) | [control flow graph](#control-flow-graph) | [cross compiler](#cross-compiler) | [current function](#current-function) | [debug counter](#debug-counter) | [default definition](#default-definition) | [define_insn](#define_insn) | [definition](#definition) | [directive](#directive) | [dominance](#dominance) | [driver](#driver) | [dump file](#dump-file) | [edge](#edge) | [effective target](#effective-target) | [excess errors](#excess-errors) | [expand](#expand) | [final](#final) | [front end](#front-end) | [garbage collector](#garbage-collector) | [gate](#gate) | [generated file](#generated-file) | [gengtype](#gengtype) | [gimplification](#gimplification) | [hard register](#hard-register) | [immediate dominator](#immediate-dominator) | [inferior call](#inferior-call) | [insn](#insn) | [interference](#interference) | [live range](#live-range) | [loop](#loop) | [machine description](#machine-description) | [machine mode](#machine-mode) | [middle end](#middle-end) | [mode iterator](#mode-iterator) | [optimization level](#optimization-level) | [out of SSA](#out-of-ssa) | [out of tree build](#out-of-tree-build) | [output template](#output-template) | [param](#param) | [pass](#pass) | [pass manager](#pass-manager) | [phi node](#phi-node) | [poly_int](#poly_int) | [port](#port) | [pretty printer](#pretty-printer) | [pseudo register](#pseudo-register) | [register allocation](#register-allocation) | [register class](#register-class) | [register pressure](#register-pressure) | [section](#section) | [spec](#spec) | [spill](#spill) | [stage comparison](#stage-comparison) | [stamp file](#stamp-file) | [sum file](#sum-file) | [target hook](#target-hook) | [target triple](#target-triple) | [temporary](#temporary) | [three address form](#three-address-form) | [torture options](#torture-options) | [tree](#tree) | [use](#use) | [wide_int](#wide_int) +[DejaGnu](#dejagnu) | [GENERIC](#generic) | [GIMPLE](#gimple) | [IRA](#ira) | [LRA](#lra) | [RTL](#rtl) | [RTX](#rtx) | [SSA](#ssa) | [SSA name](#ssa-name) | [TODO flags](#todo-flags) | [allocno](#allocno) | [alternative](#alternative) | [assembler directive](#assembler-directive) | [back end](#back-end) | [basic block](#basic-block) | [bootstrap](#bootstrap) | [bubbling](#bubbling) | [build config](#build-config) | [cc1](#cc1) | [checking build](#checking-build) | [collect2](#collect2) | [constraint](#constraint) | [control flow graph](#control-flow-graph) | [cross compiler](#cross-compiler) | [current function](#current-function) | [debug counter](#debug-counter) | [default definition](#default-definition) | [define_insn](#define_insn) | [definition](#definition) | [directive](#directive) | [dominance](#dominance) | [driver](#driver) | [dump file](#dump-file) | [edge](#edge) | [effective target](#effective-target) | [excess errors](#excess-errors) | [expand](#expand) | [final](#final) | [front end](#front-end) | [garbage collector](#garbage-collector) | [gate](#gate) | [generated file](#generated-file) | [gengtype](#gengtype) | [gimplification](#gimplification) | [hard register](#hard-register) | [immediate dominator](#immediate-dominator) | [inferior call](#inferior-call) | [insn](#insn) | [interference](#interference) | [live range](#live-range) | [loop](#loop) | [machine description](#machine-description) | [machine mode](#machine-mode) | [middle end](#middle-end) | [mode iterator](#mode-iterator) | [optimization level](#optimization-level) | [out of SSA](#out-of-ssa) | [out of tree build](#out-of-tree-build) | [output template](#output-template) | [param](#param) | [pass](#pass) | [pass manager](#pass-manager) | [pass positioning](#pass-positioning) | [phi node](#phi-node) | [plugin](#plugin) | [plugin ABI](#plugin-abi) | [plugin event](#plugin-event) | [poly_int](#poly_int) | [port](#port) | [pretty printer](#pretty-printer) | [pseudo register](#pseudo-register) | [pseudo-event](#pseudo-event) | [register allocation](#register-allocation) | [register class](#register-class) | [register pressure](#register-pressure) | [section](#section) | [spec](#spec) | [spill](#spill) | [stage comparison](#stage-comparison) | [stamp file](#stamp-file) | [sum file](#sum-file) | [target hook](#target-hook) | [target triple](#target-triple) | [temporary](#temporary) | [three address form](#three-address-form) | [torture options](#torture-options) | [tree](#tree) | [use](#use) | [wide_int](#wide_int) ## Reading the source @@ -713,3 +713,55 @@ Also written `dg-require-effective-target`, `target-supports.exp`, `dg-skip-if`. A line is a state and a name, `PASS: gcc.dg/pr1234.c (test for excess errors)`, and one test file produces several of them. Five of the thirteen states make a run red and the other eight are information, and `XFAIL` in particular means a known failure that failed as expected. The right way to read one is against a previous one: a `PASS` that became a `FAIL` is a regression, and a name that is in the old file and not the new one is a test that stopped being run, which the summary block at the bottom cannot show you and which is the more dangerous of the two. Also written `.sum`, `.log`, `XFAIL`. Taught in B04. See also [excess errors](#excess-errors), [effective target](#effective-target). + +## Extending the compiler + +Words you need to load code of your own into GCC, put a pass where you want it, and understand why the thing you are writing against is not an API. B05 is the lesson. + +### plugin + +**A shared object loaded into `cc1` at startup, which registers functions to be called at named points during a compilation.** + +`-fplugin=./thing.so` and the compiler proper `dlopen`s it with `RTLD_NOW`, looks for a symbol called `plugin_is_GPL_compatible` by name, and calls `plugin_init`. Everything a plugin can do follows from being inside the same process: it sees the real IR, calls the real functions, and crashes the real compiler. There are three ways it can be refused, all of them before it runs a line of its own code, and all three stop the compilation rather than warning and carrying on. It is the only supported way to observe or change what GCC does without patching GCC. + +Also written `-fplugin=`, `plugin_init`, `-fplugin-arg-`. Taught in B05. See also [plugin event](#plugin-event), [plugin ABI](#plugin-abi). In the source: [`gcc/plugin.cc:699@releases/gcc-16.2.0`](https://github.com/gcc-mirror/gcc/blob/releases/gcc-16.2.0/gcc/plugin.cc#L699). + +### plugin event + +**A named point in a compilation where GCC calls whatever a plugin registered for it.** + +`gcc/plugin.def` is a file of twenty six one-line macro calls, and the order of that file is the ABI, because the enumerator's value is the index into the callback table. Twenty three of them are fired, from twenty nine places, and what arrives with each one is a `void *` whose real type is written in the call site and nowhere else. Firing an event is a walk down a linked list of callbacks in registration order, with no return value read, so a plugin cannot refuse an event or stop another plugin from seeing it. + +Also written `DEFEVENT`, `register_callback`, `invoke_plugin_callbacks`. Taught in B05. See also [plugin](#plugin), [pseudo-event](#pseudo-event). In the source: [`gcc/plugin.def:20@releases/gcc-16.2.0`](https://github.com/gcc-mirror/gcc/blob/releases/gcc-16.2.0/gcc/plugin.def#L20). + +### pseudo-event + +**One of the three names in the event list that is never fired, and is instead acted on at the moment you register for it.** + +`PLUGIN_PASS_MANAGER_SETUP`, `PLUGIN_INFO` and `PLUGIN_REGISTER_GGC_ROOTS` are handled inside `register_callback` itself: it asserts the callback is null and uses the user data argument straight away. So registering a pass is spelled the same way as registering a callback and does something else entirely, which is the single most confusing thing about the mechanism, and the reason a plugin that passes a function pointer alongside a `register_pass_info` gets an assertion failure rather than a diagnostic. + +Also written `PLUGIN_PASS_MANAGER_SETUP`, `register_callback`. Taught in B05. See also [plugin event](#plugin-event), [pass positioning](#pass-positioning). In the source: [`gcc/plugin.cc:458@releases/gcc-16.2.0`](https://github.com/gcc-mirror/gcc/blob/releases/gcc-16.2.0/gcc/plugin.cc#L458). + +### pass positioning + +**The four fields that say where in the pipeline a plugin's pass goes: which pass to hang it on, which run of that pass, and before, after or instead of.** + +`struct register_pass_info` holds the new pass, a `reference_pass_name`, a `ref_pass_instance_number` where zero means every instance and one means the first, and a `pos_op` of `PASS_POS_INSERT_AFTER`, `PASS_POS_INSERT_BEFORE` or `PASS_POS_REPLACE`. The name to give is the pass name and not the dump name: `-fdump-tree-cddce1` is the pass called `cddce` on its first instance, and a reference to `cddce1` matches nothing. A reference that matches nothing is a fatal error at registration, which is the one mistake in this area that tells you about itself. + +Also written `register_pass_info`, `PASS_POS_INSERT_AFTER`, `position_pass`. Taught in B05. See also [pass](#pass), [pseudo-event](#pseudo-event). In the source: [`gcc/tree-pass.h:328@releases/gcc-16.2.0`](https://github.com/gcc-mirror/gcc/blob/releases/gcc-16.2.0/gcc/tree-pass.h#L328). + +### plugin ABI + +**The unwritten contract between a plugin and the compiler that loads it, which is every private header GCC has and every option it was configured with.** + +`plugin_default_version_check` compares five fields, not one: the base version, the datestamp, the development phase, the revision and the whole configuration argument string. Two compilers of the same version built with different `--enable` flags have incompatible plugin ABIs, because those flags change struct layouts. That is why a plugin has to be built by the compiler that will load it, why the check is the plugin's own job rather than the compiler's, and why there is no such thing as shipping a binary plugin. + +Also written `plugin_default_version_check`, `plugin-version.h`, `gcc_version`. Taught in B05. See also [plugin](#plugin), [build config](#build-config). In the source: [`gcc/plugin.cc:1013@releases/gcc-16.2.0`](https://github.com/gcc-mirror/gcc/blob/releases/gcc-16.2.0/gcc/plugin.cc#L1013). + +### TODO flags + +**What a pass tells the pass manager to do after it has run, returned as a bit mask from `execute`.** + +`TODO_cleanup_cfg`, `TODO_update_ssa`, `TODO_verify_all` and their neighbours. A pass that changed nothing returns zero. A pass that modified the IR and returned zero has left the compiler believing things that are no longer true, and the failure shows up several passes later in code that did nothing wrong, which is the most expensive mistake a first plugin can make. There is a second set, `todo_flags_start`, that runs before the pass instead, and `TODO_mark_first_instance` in it is how the pass manager knows which run of a repeated pass is the first. + +Also written `todo_flags_finish`, `TODO_update_ssa`, `pass_data`. Taught in B05. See also [pass](#pass), [pass positioning](#pass-positioning). In the source: [`gcc/tree-pass.h:238@releases/gcc-16.2.0`](https://github.com/gcc-mirror/gcc/blob/releases/gcc-16.2.0/gcc/tree-pass.h#L238). diff --git a/README.md b/README.md index 2f9d619..29726bf 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,9 @@ Lessons do not invent a fresh example each time either. There are three programs | B02 | [Building it three times, and the one thing that proves](https://github.com/tamnd/gcc-internals/blob/main/lessons/b02-the-bootstrap/b02.ipynb) | Why a compiler is built three times and compared twice, what that comparison can and cannot catch, the six files it is told to ignore, and a real stage comparison failure induced on purpose so you can see what it looks like before it happens to you | M2 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/gcc-internals/blob/main/lessons/b02-the-bootstrap/b02.ipynb) | | B03 | [A debugger on the compiler, and the counter for when you cannot have one](https://github.com/tamnd/gcc-internals/blob/main/lessons/b03-the-debugger/b03.ipynb) | A real gdb session against a real cc1, recorded command by command: what the compiler's own gdbinit did before you typed anything, how to stop on one pass out of several hundred, how to read a function from a breakpoint, and how to find the exact transformation that broke your code by bisecting a debug counter instead | M2 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/gcc-internals/blob/main/lessons/b03-the-debugger/b03.ipynb) | | B04 | [Forty thousand tests, and how to run one of them](https://github.com/tamnd/gcc-internals/blob/main/lessons/b04-the-test-suite/b04.ipynb) | The GCC test suite taken apart: what a directive is, why the command line is not what the file says, the subtractive rule that makes a test fail for output nobody asked about, the six compilations every torture test costs, how to run one file without running the other thirty five thousand, and the one question worth asking of a .sum | M2 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/gcc-internals/blob/main/lessons/b04-the-test-suite/b04.ipynb) | +| B05 | [Sixty lines of C++, and you are inside the compiler](https://github.com/tamnd/gcc-internals/blob/main/lessons/b05-the-plugin/b05.ipynb) | GCC's plugin mechanism from the outside in: the three things a plugin has to have, the three ways it is refused, what an event actually is, a GIMPLE pass of your own inserted after ssa with its own dump file, switching one of GCC's passes off from outside and watching the assembly move, and why the thing you are writing against is not an API | M2 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/gcc-internals/blob/main/lessons/b05-the-plugin/b05.ipynb) | -16 of 96 written. +17 of 96 written. This table is generated from the lessons, by the same command that builds them, so it cannot list a lesson that does not exist or miss one that does. T05 is the pilot and it is deliberately in the middle of Part I rather than at the start, because everything the course promises has to be true of a hard lesson before it is worth writing the easy ones. diff --git a/citations.lock.json b/citations.lock.json index 7136952..8613332 100644 --- a/citations.lock.json +++ b/citations.lock.json @@ -1123,6 +1123,10 @@ "hash": "32cf5ac12ec10166", "text": " invoke_plugin_callbacks (PLUGIN_NEW_PASS, new_pass);" }, + "gcc/passes.cc:1377@releases/gcc-16.2.0": { + "hash": "e2440f87e01d0e17", + "text": "/* Insert the pass at the proper position. Return true if the pass" + }, "gcc/passes.cc:1480@releases/gcc-16.2.0": { "hash": "51646e5bb483387d", "text": "register_pass (struct register_pass_info *pass_info)" @@ -1287,6 +1291,10 @@ "hash": "bc750d58492694fc", "text": "\t NEXT_PASS (pass_remove_cgraph_callee_edges);" }, + "gcc/plugin.cc:1004@releases/gcc-16.2.0": { + "hash": "adaccefb6d38829e", + "text": "/* The default version check. Compares every field in VERSION. */" + }, "gcc/plugin.cc:1007@releases/gcc-16.2.0": { "hash": "d5e15bb6d9a8357f", "text": "plugin_default_version_check (struct plugin_gcc_version *gcc_version," @@ -1295,6 +1303,10 @@ "hash": "4269ae3ebcee8a0c", "text": "" }, + "gcc/plugin.cc:1013@releases/gcc-16.2.0": { + "hash": "62d69450ab9d232d", + "text": " if (strcmp (gcc_version->basever, plugin_version->basever))" + }, "gcc/plugin.cc:1043@releases/gcc-16.2.0": { "hash": "ac5b6b08ed253871", "text": "default_plugin_dir_name (void)" @@ -1343,6 +1355,10 @@ "hash": "5bf83c681ec1db72", "text": "get_named_event_id (const char *name, enum insert_option insert)" }, + "gcc/plugin.cc:450@releases/gcc-16.2.0": { + "hash": "bcf5f3c03b95cac3", + "text": "void" + }, "gcc/plugin.cc:458@releases/gcc-16.2.0": { "hash": "2684a70ad2d92b09", "text": " case PLUGIN_PASS_MANAGER_SETUP:" @@ -1379,6 +1395,10 @@ "hash": "dec9f0ac799e2524", "text": "# define DEFEVENT(NAME) GCC_PLUGIN_STRINGIFY1 (NAME)," }, + "gcc/plugin.cc:582@releases/gcc-16.2.0": { + "hash": "797c574a6f4323c9", + "text": " {" + }, "gcc/plugin.cc:596@releases/gcc-16.2.0": { "hash": "3c735a762c2877de", "text": " gcc_assert (false);" @@ -1391,10 +1411,22 @@ "hash": "de7650830617d45d", "text": "try_init_one_plugin (struct plugin_name_args *plugin)" }, + "gcc/plugin.cc:699@releases/gcc-16.2.0": { + "hash": "12f26bafa7944fcd", + "text": " /* We use RTLD_NOW to accelerate binding and detect any mismatch" + }, + "gcc/plugin.cc:713@releases/gcc-16.2.0": { + "hash": "2c32f30d11101c3a", + "text": " /* Check the plugin license. */" + }, "gcc/plugin.cc:716@releases/gcc-16.2.0": { "hash": "e1b04ee5a1c4bbf3", "text": "\t\t \"plugin %s is not licensed under a GPL-compatible license:\"" }, + "gcc/plugin.cc:731@releases/gcc-16.2.0": { + "hash": "cfd0fa2a390035aa", + "text": " /* Call the plugin-provided initialization routine with the arguments. */" + }, "gcc/plugin.cc:738@releases/gcc-16.2.0": { "hash": "d042a48384209bbc", "text": " /* leak dl_handle on purpose to ensure the plugin is loaded for the" @@ -1415,6 +1447,10 @@ "hash": "df5740881a86dc4c", "text": " fnotice (stderr, \"*** WARNING *** there are active plugins, do not report\"" }, + "gcc/plugin.def:20@releases/gcc-16.2.0": { + "hash": "468bf48d8250da5b", + "text": "/* Called before parsing the body of a function. */" + }, "gcc/plugin.h:153@releases/gcc-16.2.0": { "hash": "f1cdd28a83b226c7", "text": "extern int plugin_is_GPL_compatible;" @@ -1759,6 +1795,10 @@ "hash": "8480add6167edcd4", "text": "#define PROP_gimple \\" }, + "gcc/tree-pass.h:238@releases/gcc-16.2.0": { + "hash": "f9a020dff3f3bbd5", + "text": "/* To-do flags. */" + }, "gcc/tree-pass.h:240@releases/gcc-16.2.0": { "hash": "78c56868081ddba0", "text": "#define TODO_do_not_ggc_collect\t\t(1 << 1)" diff --git a/corpora/plug/b05.json b/corpora/plug/b05.json new file mode 100644 index 0000000..cb81e8b --- /dev/null +++ b/corpora/plug/b05.json @@ -0,0 +1,197 @@ +{ + "recorded": "2026-09-06", + "tag": "releases/gcc-16.2.0", + "compiler": "gcc-16 (Homebrew GCC 16.2.0) 16.2.0", + "target": "aarch64-apple-darwin24", + "probe": "GCC gcc-16\nversion 16.2.0\nplugin headers $(brew --prefix)/Cellar/gcc/16.2.0/bin/../lib/gcc/current/gcc/aarch64-apple-darwin24/16/plugin/include\ngcc-plugin.h present\nuname Darwin\ncompiles c++ yes\ngmp.h on path no\nCPPFLAGS -I$(brew --prefix)/Cellar/gcc/16.2.0/bin/../lib/gcc/current/gcc/aarch64-apple-darwin24/16/plugin/include -I$(brew --prefix)/include -I$(brew --prefix)/opt/llvm/include\nlink flags -Wl,-undefined,dynamic_lookup\n", + "pipeline": { + "total": 398, + "index": 22, + "around": [ + " ipa-build_ssa_passes : ON", + " tree-fixup_cfg1 : ON", + " tree-ssa : ON", + " tree-gxcount : ON", + " tree-walloca1 : ON", + " tree-warn-printf : OFF", + " *nonnullcmp : OFF" + ] + }, + "stream": "{\"seq\":0,\"event\":\"pass-start\",\"pass\":\"*warn_unused_result\",\"pass_number\":-1,\"function\":\"f\",\"properties\":16385,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":null}\n{\"seq\":1,\"event\":\"pass-end\",\"pass\":\"*warn_unused_result\",\"pass_number\":-1,\"function\":\"f\",\"properties\":16385,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":0.000012000}\n{\"seq\":2,\"event\":\"pass-start\",\"pass\":\"omplower\",\"pass_number\":14,\"function\":\"f\",\"properties\":16385,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":null}\n{\"seq\":3,\"event\":\"pass-end\",\"pass\":\"omplower\",\"pass_number\":14,\"function\":\"f\",\"properties\":82177,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":0.000000000}\n{\"seq\":4,\"event\":\"pass-start\",\"pass\":\"lower\",\"pass_number\":15,\"function\":\"f\",\"properties\":82177,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":null}\n{\"seq\":5,\"event\":\"pass-end\",\"pass\":\"lower\",\"pass_number\":15,\"function\":\"f\",\"properties\":82179,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":0.000006000}\n{\"seq\":6,\"event\":\"pass-start\",\"pass\":\"eh\",\"pass_number\":18,\"function\":\"f\",\"properties\":82179,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":null}\n{\"seq\":7,\"event\":\"pass-end\",\"pass\":\"eh\",\"pass_number\":18,\"function\":\"f\",\"properties\":82183,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":0.000007000}\n{\"seq\":8,\"event\":\"pass-start\",\"pass\":\"cfg\",\"pass_number\":20,\"function\":\"f\",\"properties\":82183,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":null}\n{\"seq\":9,\"event\":\"pass-end\",\"pass\":\"cfg\",\"pass_number\":20,\"function\":\"f\",\"properties\":84239,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":0.000078000}\n{\"seq\":10,\"event\":\"pass-start\",\"pass\":\"*warn_function_return\",\"pass_number\":-1,\"function\":\"f\",\"properties\":84239,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":11,\"event\":\"pass-end\",\"pass\":\"*warn_function_return\",\"pass_number\":-1,\"function\":\"f\",\"properties\":84239,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":0.000000000}\n{\"seq\":12,\"event\":\"pass-start\",\"pass\":\"ompexp\",\"pass_number\":22,\"function\":\"f\",\"properties\":84239,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":13,\"event\":\"pass-end\",\"pass\":\"ompexp\",\"pass_number\":22,\"function\":\"f\",\"properties\":92431,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":0.000000000}\n{\"seq\":14,\"event\":\"pass-start\",\"pass\":\"*build_cgraph_edges\",\"pass_number\":-1,\"function\":\"f\",\"properties\":92431,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":15,\"event\":\"pass-end\",\"pass\":\"*build_cgraph_edges\",\"pass_number\":-1,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":0.000026000}\n{\"seq\":16,\"event\":\"pass-start\",\"pass\":\"*free_lang_data\",\"pass_number\":-1,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":null}\n{\"seq\":17,\"event\":\"pass-end\",\"pass\":\"*free_lang_data\",\"pass_number\":-1,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":0.000003000}\n{\"seq\":18,\"event\":\"pass-start\",\"pass\":\"visibility\",\"pass_number\":24,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":null}\n{\"seq\":19,\"event\":\"pass-end\",\"pass\":\"visibility\",\"pass_number\":24,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":0.000019000}\n{\"seq\":20,\"event\":\"pass-start\",\"pass\":\"build_ssa_passes\",\"pass_number\":26,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":null}\n{\"seq\":21,\"event\":\"pass-end\",\"pass\":\"build_ssa_passes\",\"pass_number\":26,\"function\":\"f\",\"properties\":92431,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":0.000011000}\n{\"seq\":22,\"event\":\"pass-start\",\"pass\":\"fixup_cfg\",\"pass_number\":27,\"function\":\"f\",\"properties\":92431,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":23,\"event\":\"pass-end\",\"pass\":\"fixup_cfg\",\"pass_number\":27,\"function\":\"f\",\"properties\":92431,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":0.000002000}\n{\"seq\":24,\"event\":\"pass-start\",\"pass\":\"ssa\",\"pass_number\":28,\"function\":\"f\",\"properties\":92431,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":25,\"event\":\"pass-end\",\"pass\":\"ssa\",\"pass_number\":28,\"function\":\"f\",\"properties\":92463,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":0.000048000}\n{\"seq\":26,\"event\":\"pass-start\",\"pass\":\"walloca\",\"pass_number\":29,\"function\":\"f\",\"properties\":92463,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":27,\"event\":\"pass-end\",\"pass\":\"walloca\",\"pass_number\":29,\"function\":\"f\",\"properties\":92463,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":0.000068000}\n{\"seq\":28,\"event\":\"pass-start\",\"pass\":\"waccess\",\"pass_number\":32,\"function\":\"f\",\"properties\":92463,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":29,\"event\":\"pass-end\",\"pass\":\"waccess\",\"pass_number\":32,\"function\":\"f\",\"properties\":92463,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":0.000022000}\n{\"seq\":30,\"event\":\"pass-start\",\"pass\":\"nothrow\",\"pass_number\":34,\"function\":\"f\",\"properties\":92463,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":31,\"event\":\"pass-end\",\"pass\":\"nothrow\",\"pass_number\":34,\"function\":\"f\",\"properties\":92463,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":0.000002000}\n{\"seq\":32,\"event\":\"pass-start\",\"pass\":\"*rebuild_cgraph_edges\",\"pass_number\":-1,\"function\":\"f\",\"properties\":92463,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":33,\"event\":\"pass-end\",\"pass\":\"*rebuild_cgraph_edges\",\"pass_number\":-1,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":0.000007000}\n{\"seq\":34,\"event\":\"pass-start\",\"pass\":\"targetclone\",\"pass_number\":35,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":null}\n{\"seq\":35,\"event\":\"pass-end\",\"pass\":\"targetclone\",\"pass_number\":35,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":0.000003000}\n{\"seq\":36,\"event\":\"pass-start\",\"pass\":\"opt_local_passes\",\"pass_number\":36,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":null}\n{\"seq\":37,\"event\":\"pass-end\",\"pass\":\"opt_local_passes\",\"pass_number\":36,\"function\":\"f\",\"properties\":92463,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":0.000001000}\n{\"seq\":38,\"event\":\"pass-start\",\"pass\":\"fixup_cfg\",\"pass_number\":37,\"function\":\"f\",\"properties\":92463,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":39,\"event\":\"pass-end\",\"pass\":\"fixup_cfg\",\"pass_number\":37,\"function\":\"f\",\"properties\":92463,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":0.000001000}\n{\"seq\":40,\"event\":\"pass-start\",\"pass\":\"*rebuild_cgraph_edges\",\"pass_number\":0,\"function\":\"f\",\"properties\":92463,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":41,\"event\":\"pass-end\",\"pass\":\"*rebuild_cgraph_edges\",\"pass_number\":0,\"function\":\"f\",\"properties\":92463,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":0.000001000}\n{\"seq\":42,\"event\":\"pass-start\",\"pass\":\"local-fnsummary\",\"pass_number\":38,\"function\":\"f\",\"properties\":92463,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":43,\"event\":\"pass-end\",\"pass\":\"local-fnsummary\",\"pass_number\":38,\"function\":\"f\",\"properties\":92463,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":0.000064000}\n{\"seq\":44,\"event\":\"pass-start\",\"pass\":\"einline\",\"pass_number\":39,\"function\":\"f\",\"properties\":92463,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":45,\"event\":\"pass-end\",\"pass\":\"einline\",\"pass_number\":39,\"function\":\"f\",\"properties\":92463,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":0.000002000}\n{\"seq\":46,\"event\":\"pass-start\",\"pass\":\"early_optimizations\",\"pass_number\":40,\"function\":\"f\",\"properties\":92463,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":47,\"event\":\"pass-end\",\"pass\":\"early_optimizations\",\"pass_number\":40,\"function\":\"f\",\"properties\":92463,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":0.000001000}\n{\"seq\":48,\"event\":\"pass-start\",\"pass\":\"*remove_cgraph_callee_edges\",\"pass_number\":-1,\"function\":\"f\",\"properties\":92463,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":49,\"event\":\"pass-end\",\"pass\":\"*remove_cgraph_callee_edges\",\"pass_number\":-1,\"function\":\"f\",\"properties\":92463,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":0.000000000}\n{\"seq\":50,\"event\":\"pass-start\",\"pass\":\"early_objsz\",\"pass_number\":41,\"function\":\"f\",\"properties\":92463,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":51,\"event\":\"pass-end\",\"pass\":\"early_objsz\",\"pass_number\":41,\"function\":\"f\",\"properties\":92463,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":0.000001000}\n{\"seq\":52,\"event\":\"pass-start\",\"pass\":\"ccp\",\"pass_number\":42,\"function\":\"f\",\"properties\":92463,\"statements\":7,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":53,\"event\":\"pass-end\",\"pass\":\"ccp\",\"pass_number\":42,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000170000}\n{\"seq\":54,\"event\":\"pass-start\",\"pass\":\"forwprop\",\"pass_number\":43,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":55,\"event\":\"pass-end\",\"pass\":\"forwprop\",\"pass_number\":43,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000024000}\n{\"seq\":56,\"event\":\"pass-start\",\"pass\":\"ethread\",\"pass_number\":44,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":57,\"event\":\"pass-end\",\"pass\":\"ethread\",\"pass_number\":44,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000110000}\n{\"seq\":58,\"event\":\"pass-start\",\"pass\":\"esra\",\"pass_number\":45,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":59,\"event\":\"pass-end\",\"pass\":\"esra\",\"pass_number\":45,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000002000}\n{\"seq\":60,\"event\":\"pass-start\",\"pass\":\"ealias\",\"pass_number\":46,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":61,\"event\":\"pass-end\",\"pass\":\"ealias\",\"pass_number\":46,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000062000}\n{\"seq\":62,\"event\":\"pass-start\",\"pass\":\"phiprop\",\"pass_number\":47,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":63,\"event\":\"pass-end\",\"pass\":\"phiprop\",\"pass_number\":47,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000002000}\n{\"seq\":64,\"event\":\"pass-start\",\"pass\":\"fre\",\"pass_number\":48,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":65,\"event\":\"pass-end\",\"pass\":\"fre\",\"pass_number\":48,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000073000}\n{\"seq\":66,\"event\":\"pass-start\",\"pass\":\"evrp\",\"pass_number\":49,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":67,\"event\":\"pass-end\",\"pass\":\"evrp\",\"pass_number\":49,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000332000}\n{\"seq\":68,\"event\":\"pass-start\",\"pass\":\"mergephi\",\"pass_number\":50,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":69,\"event\":\"pass-end\",\"pass\":\"mergephi\",\"pass_number\":50,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000001000}\n{\"seq\":70,\"event\":\"pass-start\",\"pass\":\"dse\",\"pass_number\":51,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":71,\"event\":\"pass-end\",\"pass\":\"dse\",\"pass_number\":51,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000003000}\n{\"seq\":72,\"event\":\"pass-start\",\"pass\":\"cddce\",\"pass_number\":52,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":73,\"event\":\"pass-end\",\"pass\":\"cddce\",\"pass_number\":52,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000021000}\n{\"seq\":74,\"event\":\"pass-start\",\"pass\":\"phiopt\",\"pass_number\":53,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":75,\"event\":\"pass-end\",\"pass\":\"phiopt\",\"pass_number\":53,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000006000}\n{\"seq\":76,\"event\":\"pass-start\",\"pass\":\"tailr\",\"pass_number\":55,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":77,\"event\":\"pass-end\",\"pass\":\"tailr\",\"pass_number\":55,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000008000}\n{\"seq\":78,\"event\":\"pass-start\",\"pass\":\"iftoswitch\",\"pass_number\":56,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":79,\"event\":\"pass-end\",\"pass\":\"iftoswitch\",\"pass_number\":56,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000008000}\n{\"seq\":80,\"event\":\"pass-start\",\"pass\":\"switchconv\",\"pass_number\":57,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":81,\"event\":\"pass-end\",\"pass\":\"switchconv\",\"pass_number\":57,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000001000}\n{\"seq\":82,\"event\":\"pass-start\",\"pass\":\"sccopy\",\"pass_number\":58,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":83,\"event\":\"pass-end\",\"pass\":\"sccopy\",\"pass_number\":58,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000013000}\n{\"seq\":84,\"event\":\"pass-start\",\"pass\":\"profile_estimate\",\"pass_number\":59,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":85,\"event\":\"pass-end\",\"pass\":\"profile_estimate\",\"pass_number\":59,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000061000}\n{\"seq\":86,\"event\":\"pass-start\",\"pass\":\"local-pure-const\",\"pass_number\":60,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":87,\"event\":\"pass-end\",\"pass\":\"local-pure-const\",\"pass_number\":60,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000012000}\n{\"seq\":88,\"event\":\"pass-start\",\"pass\":\"modref\",\"pass_number\":61,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":89,\"event\":\"pass-end\",\"pass\":\"modref\",\"pass_number\":61,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000033000}\n{\"seq\":90,\"event\":\"pass-start\",\"pass\":\"fnsplit\",\"pass_number\":62,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":91,\"event\":\"pass-end\",\"pass\":\"fnsplit\",\"pass_number\":62,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000001000}\n{\"seq\":92,\"event\":\"pass-start\",\"pass\":\"*strip_predict_hints\",\"pass_number\":-1,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":93,\"event\":\"pass-end\",\"pass\":\"*strip_predict_hints\",\"pass_number\":-1,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000001000}\n{\"seq\":94,\"event\":\"pass-start\",\"pass\":\"release_ssa\",\"pass_number\":63,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":95,\"event\":\"pass-end\",\"pass\":\"release_ssa\",\"pass_number\":63,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000002000}\n{\"seq\":96,\"event\":\"pass-start\",\"pass\":\"*rebuild_cgraph_edges\",\"pass_number\":0,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":97,\"event\":\"pass-end\",\"pass\":\"*rebuild_cgraph_edges\",\"pass_number\":0,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000001000}\n{\"seq\":98,\"event\":\"pass-start\",\"pass\":\"local-fnsummary\",\"pass_number\":64,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":99,\"event\":\"pass-end\",\"pass\":\"local-fnsummary\",\"pass_number\":64,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":0.000013000}\n{\"seq\":100,\"event\":\"pass-start\",\"pass\":\"remove_symbols\",\"pass_number\":65,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":null}\n{\"seq\":101,\"event\":\"pass-end\",\"pass\":\"remove_symbols\",\"pass_number\":65,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":0.000007000}\n{\"seq\":102,\"event\":\"pass-start\",\"pass\":\"targetclone\",\"pass_number\":78,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":null}\n{\"seq\":103,\"event\":\"pass-end\",\"pass\":\"targetclone\",\"pass_number\":78,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":0.000012000}\n{\"seq\":104,\"event\":\"pass-start\",\"pass\":\"free-fnsummary\",\"pass_number\":83,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":null}\n{\"seq\":105,\"event\":\"pass-end\",\"pass\":\"free-fnsummary\",\"pass_number\":83,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":0.000004000}\n{\"seq\":106,\"event\":\"pass-start\",\"pass\":\"emutls\",\"pass_number\":86,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":null}\n{\"seq\":107,\"event\":\"pass-end\",\"pass\":\"emutls\",\"pass_number\":86,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":0.000096000}\n{\"seq\":108,\"event\":\"pass-start\",\"pass\":\"whole-program\",\"pass_number\":91,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":null}\n{\"seq\":109,\"event\":\"pass-end\",\"pass\":\"whole-program\",\"pass_number\":91,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":0.000003000}\n{\"seq\":110,\"event\":\"pass-start\",\"pass\":\"profile_estimate\",\"pass_number\":92,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":null}\n{\"seq\":111,\"event\":\"pass-end\",\"pass\":\"profile_estimate\",\"pass_number\":92,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":0.000003000}\n{\"seq\":112,\"event\":\"pass-start\",\"pass\":\"icf\",\"pass_number\":93,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":null}\n{\"seq\":113,\"event\":\"pass-end\",\"pass\":\"icf\",\"pass_number\":93,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":0.000009000}\n{\"seq\":114,\"event\":\"pass-start\",\"pass\":\"devirt\",\"pass_number\":94,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":null}\n{\"seq\":115,\"event\":\"pass-end\",\"pass\":\"devirt\",\"pass_number\":94,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":0.000006000}\n{\"seq\":116,\"event\":\"pass-start\",\"pass\":\"cdtor\",\"pass_number\":95,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":null}\n{\"seq\":117,\"event\":\"pass-end\",\"pass\":\"cdtor\",\"pass_number\":95,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":0.000001000}\n{\"seq\":118,\"event\":\"pass-start\",\"pass\":\"cp\",\"pass_number\":96,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":null}\n{\"seq\":119,\"event\":\"pass-end\",\"pass\":\"cp\",\"pass_number\":96,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":0.000024000}\n{\"seq\":120,\"event\":\"pass-start\",\"pass\":\"sra\",\"pass_number\":97,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":null}\n{\"seq\":121,\"event\":\"pass-end\",\"pass\":\"sra\",\"pass_number\":97,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":0.000006000}\n{\"seq\":122,\"event\":\"pass-start\",\"pass\":\"fnsummary\",\"pass_number\":98,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":null}\n{\"seq\":123,\"event\":\"pass-end\",\"pass\":\"fnsummary\",\"pass_number\":98,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":0.000001000}\n{\"seq\":124,\"event\":\"pass-start\",\"pass\":\"inline\",\"pass_number\":99,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":null}\n{\"seq\":125,\"event\":\"pass-end\",\"pass\":\"inline\",\"pass_number\":99,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":0.000006000}\n{\"seq\":126,\"event\":\"pass-start\",\"pass\":\"pure-const\",\"pass_number\":101,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":null}\n{\"seq\":127,\"event\":\"pass-end\",\"pass\":\"pure-const\",\"pass_number\":101,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":0.000004000}\n{\"seq\":128,\"event\":\"pass-start\",\"pass\":\"modref\",\"pass_number\":102,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":null}\n{\"seq\":129,\"event\":\"pass-end\",\"pass\":\"modref\",\"pass_number\":102,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":0.000004000}\n{\"seq\":130,\"event\":\"pass-start\",\"pass\":\"free-fnsummary\",\"pass_number\":103,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":null}\n{\"seq\":131,\"event\":\"pass-end\",\"pass\":\"free-fnsummary\",\"pass_number\":103,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":0.000003000}\n{\"seq\":132,\"event\":\"pass-start\",\"pass\":\"static-var\",\"pass_number\":104,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":null}\n{\"seq\":133,\"event\":\"pass-end\",\"pass\":\"static-var\",\"pass_number\":104,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":0.000006000}\n{\"seq\":134,\"event\":\"pass-start\",\"pass\":\"single-use\",\"pass_number\":105,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":null}\n{\"seq\":135,\"event\":\"pass-end\",\"pass\":\"single-use\",\"pass_number\":105,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":0.000003000}\n{\"seq\":136,\"event\":\"pass-start\",\"pass\":\"simdclone\",\"pass_number\":108,\"function\":null,\"properties\":null,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":null}\n{\"seq\":137,\"event\":\"pass-end\",\"pass\":\"simdclone\",\"pass_number\":108,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.004350000}\n{\"seq\":138,\"event\":\"pass-start\",\"pass\":\"fixup_cfg\",\"pass_number\":109,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":139,\"event\":\"pass-end\",\"pass\":\"fixup_cfg\",\"pass_number\":109,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000002000}\n{\"seq\":140,\"event\":\"pass-start\",\"pass\":\"adjust_alignment\",\"pass_number\":116,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":141,\"event\":\"pass-end\",\"pass\":\"adjust_alignment\",\"pass_number\":116,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000001000}\n{\"seq\":142,\"event\":\"pass-start\",\"pass\":\"*all_optimizations\",\"pass_number\":-1,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":143,\"event\":\"pass-end\",\"pass\":\"*all_optimizations\",\"pass_number\":-1,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000000000}\n{\"seq\":144,\"event\":\"pass-start\",\"pass\":\"*remove_cgraph_callee_edges\",\"pass_number\":0,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":145,\"event\":\"pass-end\",\"pass\":\"*remove_cgraph_callee_edges\",\"pass_number\":0,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000001000}\n{\"seq\":146,\"event\":\"pass-start\",\"pass\":\"*strip_predict_hints\",\"pass_number\":0,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":147,\"event\":\"pass-end\",\"pass\":\"*strip_predict_hints\",\"pass_number\":0,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000001000}\n{\"seq\":148,\"event\":\"pass-start\",\"pass\":\"ccp\",\"pass_number\":118,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":149,\"event\":\"pass-end\",\"pass\":\"ccp\",\"pass_number\":118,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000063000}\n{\"seq\":150,\"event\":\"pass-start\",\"pass\":\"objsz\",\"pass_number\":119,\"function\":\"f\",\"properties\":92463,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":151,\"event\":\"pass-end\",\"pass\":\"objsz\",\"pass_number\":119,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000001000}\n{\"seq\":152,\"event\":\"pass-start\",\"pass\":\"waccess\",\"pass_number\":121,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":153,\"event\":\"pass-end\",\"pass\":\"waccess\",\"pass_number\":121,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000012000}\n{\"seq\":154,\"event\":\"pass-start\",\"pass\":\"rebuild_frequencies\",\"pass_number\":122,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":155,\"event\":\"pass-end\",\"pass\":\"rebuild_frequencies\",\"pass_number\":122,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000008000}\n{\"seq\":156,\"event\":\"pass-start\",\"pass\":\"cunrolli\",\"pass_number\":123,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":157,\"event\":\"pass-end\",\"pass\":\"cunrolli\",\"pass_number\":123,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000046000}\n{\"seq\":158,\"event\":\"pass-start\",\"pass\":\"backprop\",\"pass_number\":124,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":159,\"event\":\"pass-end\",\"pass\":\"backprop\",\"pass_number\":124,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000004000}\n{\"seq\":160,\"event\":\"pass-start\",\"pass\":\"phiprop\",\"pass_number\":125,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":161,\"event\":\"pass-end\",\"pass\":\"phiprop\",\"pass_number\":125,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000001000}\n{\"seq\":162,\"event\":\"pass-start\",\"pass\":\"forwprop\",\"pass_number\":126,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":163,\"event\":\"pass-end\",\"pass\":\"forwprop\",\"pass_number\":126,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000006000}\n{\"seq\":164,\"event\":\"pass-start\",\"pass\":\"alias\",\"pass_number\":127,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":165,\"event\":\"pass-end\",\"pass\":\"alias\",\"pass_number\":127,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000061000}\n{\"seq\":166,\"event\":\"pass-start\",\"pass\":\"retslot\",\"pass_number\":128,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":167,\"event\":\"pass-end\",\"pass\":\"retslot\",\"pass_number\":128,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000000000}\n{\"seq\":168,\"event\":\"pass-start\",\"pass\":\"fre\",\"pass_number\":129,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":169,\"event\":\"pass-end\",\"pass\":\"fre\",\"pass_number\":129,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000029000}\n{\"seq\":170,\"event\":\"pass-start\",\"pass\":\"mergephi\",\"pass_number\":130,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":171,\"event\":\"pass-end\",\"pass\":\"mergephi\",\"pass_number\":130,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000001000}\n{\"seq\":172,\"event\":\"pass-start\",\"pass\":\"threadfull\",\"pass_number\":131,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":173,\"event\":\"pass-end\",\"pass\":\"threadfull\",\"pass_number\":131,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000029000}\n{\"seq\":174,\"event\":\"pass-start\",\"pass\":\"vrp\",\"pass_number\":132,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":175,\"event\":\"pass-end\",\"pass\":\"vrp\",\"pass_number\":132,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000088000}\n{\"seq\":176,\"event\":\"pass-start\",\"pass\":\"dse\",\"pass_number\":134,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":177,\"event\":\"pass-end\",\"pass\":\"dse\",\"pass_number\":134,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000002000}\n{\"seq\":178,\"event\":\"pass-start\",\"pass\":\"dce\",\"pass_number\":135,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":179,\"event\":\"pass-end\",\"pass\":\"dce\",\"pass_number\":135,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000007000}\n{\"seq\":180,\"event\":\"pass-start\",\"pass\":\"stdarg\",\"pass_number\":136,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":181,\"event\":\"pass-end\",\"pass\":\"stdarg\",\"pass_number\":136,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000000000}\n{\"seq\":182,\"event\":\"pass-start\",\"pass\":\"cdce\",\"pass_number\":137,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":183,\"event\":\"pass-end\",\"pass\":\"cdce\",\"pass_number\":137,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000001000}\n{\"seq\":184,\"event\":\"pass-start\",\"pass\":\"cselim\",\"pass_number\":138,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":185,\"event\":\"pass-end\",\"pass\":\"cselim\",\"pass_number\":138,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000005000}\n{\"seq\":186,\"event\":\"pass-start\",\"pass\":\"copyprop\",\"pass_number\":139,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":187,\"event\":\"pass-end\",\"pass\":\"copyprop\",\"pass_number\":139,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000018000}\n{\"seq\":188,\"event\":\"pass-start\",\"pass\":\"ifcombine\",\"pass_number\":140,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":189,\"event\":\"pass-end\",\"pass\":\"ifcombine\",\"pass_number\":140,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000001000}\n{\"seq\":190,\"event\":\"pass-start\",\"pass\":\"mergephi\",\"pass_number\":141,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":191,\"event\":\"pass-end\",\"pass\":\"mergephi\",\"pass_number\":141,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000001000}\n{\"seq\":192,\"event\":\"pass-start\",\"pass\":\"phiopt\",\"pass_number\":142,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":193,\"event\":\"pass-end\",\"pass\":\"phiopt\",\"pass_number\":142,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000000000}\n{\"seq\":194,\"event\":\"pass-start\",\"pass\":\"tailr\",\"pass_number\":143,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":195,\"event\":\"pass-end\",\"pass\":\"tailr\",\"pass_number\":143,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":0.000001000}\n{\"seq\":196,\"event\":\"pass-start\",\"pass\":\"ch\",\"pass_number\":144,\"function\":\"f\",\"properties\":92479,\"statements\":4,\"insns\":null,\"blocks\":6,\"seconds\":null}\n{\"seq\":197,\"event\":\"pass-end\",\"pass\":\"ch\",\"pass_number\":144,\"function\":\"f\",\"properties\":92479,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000137000}\n{\"seq\":198,\"event\":\"pass-start\",\"pass\":\"cplxlower\",\"pass_number\":145,\"function\":\"f\",\"properties\":92479,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":199,\"event\":\"pass-end\",\"pass\":\"cplxlower\",\"pass_number\":145,\"function\":\"f\",\"properties\":93503,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000009000}\n{\"seq\":200,\"event\":\"pass-start\",\"pass\":\"bitintlower\",\"pass_number\":146,\"function\":\"f\",\"properties\":93503,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":201,\"event\":\"pass-end\",\"pass\":\"bitintlower\",\"pass_number\":146,\"function\":\"f\",\"properties\":1142079,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000014000}\n{\"seq\":202,\"event\":\"pass-start\",\"pass\":\"sra\",\"pass_number\":147,\"function\":\"f\",\"properties\":1142079,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":203,\"event\":\"pass-end\",\"pass\":\"sra\",\"pass_number\":147,\"function\":\"f\",\"properties\":1142079,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000002000}\n{\"seq\":204,\"event\":\"pass-start\",\"pass\":\"thread\",\"pass_number\":148,\"function\":\"f\",\"properties\":1142079,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":205,\"event\":\"pass-end\",\"pass\":\"thread\",\"pass_number\":148,\"function\":\"f\",\"properties\":1142079,\"statements\":5,\"insns\":null,\"blocks\":7,\"seconds\":0.000021000}\n{\"seq\":206,\"event\":\"pass-start\",\"pass\":\"dom\",\"pass_number\":149,\"function\":\"f\",\"properties\":1142079,\"statements\":5,\"insns\":null,\"blocks\":7,\"seconds\":null}\n{\"seq\":207,\"event\":\"pass-end\",\"pass\":\"dom\",\"pass_number\":149,\"function\":\"f\",\"properties\":1142079,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000097000}\n{\"seq\":208,\"event\":\"pass-start\",\"pass\":\"copyprop\",\"pass_number\":150,\"function\":\"f\",\"properties\":1142079,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":209,\"event\":\"pass-end\",\"pass\":\"copyprop\",\"pass_number\":150,\"function\":\"f\",\"properties\":1142079,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000008000}\n{\"seq\":210,\"event\":\"pass-start\",\"pass\":\"isolate-paths\",\"pass_number\":151,\"function\":\"f\",\"properties\":1142079,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":211,\"event\":\"pass-end\",\"pass\":\"isolate-paths\",\"pass_number\":151,\"function\":\"f\",\"properties\":1142079,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000005000}\n{\"seq\":212,\"event\":\"pass-start\",\"pass\":\"reassoc\",\"pass_number\":152,\"function\":\"f\",\"properties\":1142079,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":213,\"event\":\"pass-end\",\"pass\":\"reassoc\",\"pass_number\":152,\"function\":\"f\",\"properties\":1142079,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000012000}\n{\"seq\":214,\"event\":\"pass-start\",\"pass\":\"dce\",\"pass_number\":153,\"function\":\"f\",\"properties\":1142079,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":215,\"event\":\"pass-end\",\"pass\":\"dce\",\"pass_number\":153,\"function\":\"f\",\"properties\":1142079,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000002000}\n{\"seq\":216,\"event\":\"pass-start\",\"pass\":\"forwprop\",\"pass_number\":154,\"function\":\"f\",\"properties\":1142079,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":217,\"event\":\"pass-end\",\"pass\":\"forwprop\",\"pass_number\":154,\"function\":\"f\",\"properties\":1142079,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000010000}\n{\"seq\":218,\"event\":\"pass-start\",\"pass\":\"phiopt\",\"pass_number\":155,\"function\":\"f\",\"properties\":1142079,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":219,\"event\":\"pass-end\",\"pass\":\"phiopt\",\"pass_number\":155,\"function\":\"f\",\"properties\":1142079,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000000000}\n{\"seq\":220,\"event\":\"pass-start\",\"pass\":\"ccp\",\"pass_number\":156,\"function\":\"f\",\"properties\":1142079,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":221,\"event\":\"pass-end\",\"pass\":\"ccp\",\"pass_number\":156,\"function\":\"f\",\"properties\":1142079,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000055000}\n{\"seq\":222,\"event\":\"pass-start\",\"pass\":\"pow\",\"pass_number\":157,\"function\":\"f\",\"properties\":1142079,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":223,\"event\":\"pass-end\",\"pass\":\"pow\",\"pass_number\":157,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000002000}\n{\"seq\":224,\"event\":\"pass-start\",\"pass\":\"bswap\",\"pass_number\":158,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":225,\"event\":\"pass-end\",\"pass\":\"bswap\",\"pass_number\":158,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000009000}\n{\"seq\":226,\"event\":\"pass-start\",\"pass\":\"laddress\",\"pass_number\":159,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":227,\"event\":\"pass-end\",\"pass\":\"laddress\",\"pass_number\":159,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000001000}\n{\"seq\":228,\"event\":\"pass-start\",\"pass\":\"lim\",\"pass_number\":160,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":229,\"event\":\"pass-end\",\"pass\":\"lim\",\"pass_number\":160,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":7,\"seconds\":0.000023000}\n{\"seq\":230,\"event\":\"pass-start\",\"pass\":\"walloca\",\"pass_number\":161,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":7,\"seconds\":null}\n{\"seq\":231,\"event\":\"pass-end\",\"pass\":\"walloca\",\"pass_number\":161,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":7,\"seconds\":0.000005000}\n{\"seq\":232,\"event\":\"pass-start\",\"pass\":\"pre\",\"pass_number\":162,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":7,\"seconds\":null}\n{\"seq\":233,\"event\":\"pass-end\",\"pass\":\"pre\",\"pass_number\":162,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":0.000121000}\n{\"seq\":234,\"event\":\"pass-start\",\"pass\":\"sink\",\"pass_number\":163,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":null}\n{\"seq\":235,\"event\":\"pass-end\",\"pass\":\"sink\",\"pass_number\":163,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":0.000017000}\n{\"seq\":236,\"event\":\"pass-start\",\"pass\":\"dse\",\"pass_number\":167,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":null}\n{\"seq\":237,\"event\":\"pass-end\",\"pass\":\"dse\",\"pass_number\":167,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":0.000002000}\n{\"seq\":238,\"event\":\"pass-start\",\"pass\":\"dce\",\"pass_number\":168,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":null}\n{\"seq\":239,\"event\":\"pass-end\",\"pass\":\"dce\",\"pass_number\":168,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":0.000003000}\n{\"seq\":240,\"event\":\"pass-start\",\"pass\":\"fix_loops\",\"pass_number\":169,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":null}\n{\"seq\":241,\"event\":\"pass-end\",\"pass\":\"fix_loops\",\"pass_number\":169,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":0.000001000}\n{\"seq\":242,\"event\":\"pass-start\",\"pass\":\"loop\",\"pass_number\":170,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":null}\n{\"seq\":243,\"event\":\"pass-end\",\"pass\":\"loop\",\"pass_number\":170,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":0.000000000}\n{\"seq\":244,\"event\":\"pass-start\",\"pass\":\"loopinit\",\"pass_number\":171,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":null}\n{\"seq\":245,\"event\":\"pass-end\",\"pass\":\"loopinit\",\"pass_number\":171,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":0.000010000}\n{\"seq\":246,\"event\":\"pass-start\",\"pass\":\"sccp\",\"pass_number\":174,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":null}\n{\"seq\":247,\"event\":\"pass-end\",\"pass\":\"sccp\",\"pass_number\":174,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":0.000042000}\n{\"seq\":248,\"event\":\"pass-start\",\"pass\":\"cddce\",\"pass_number\":177,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":null}\n{\"seq\":249,\"event\":\"pass-end\",\"pass\":\"cddce\",\"pass_number\":177,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":0.000008000}\n{\"seq\":250,\"event\":\"pass-start\",\"pass\":\"ivcanon\",\"pass_number\":178,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":null}\n{\"seq\":251,\"event\":\"pass-end\",\"pass\":\"ivcanon\",\"pass_number\":178,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":0.000018000}\n{\"seq\":252,\"event\":\"pass-start\",\"pass\":\"ldist\",\"pass_number\":179,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":null}\n{\"seq\":253,\"event\":\"pass-end\",\"pass\":\"ldist\",\"pass_number\":179,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":0.000049000}\n{\"seq\":254,\"event\":\"pass-start\",\"pass\":\"crc\",\"pass_number\":180,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":null}\n{\"seq\":255,\"event\":\"pass-end\",\"pass\":\"crc\",\"pass_number\":180,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":0.000002000}\n{\"seq\":256,\"event\":\"pass-start\",\"pass\":\"copyprop\",\"pass_number\":182,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":null}\n{\"seq\":257,\"event\":\"pass-end\",\"pass\":\"copyprop\",\"pass_number\":182,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":0.000009000}\n{\"seq\":258,\"event\":\"pass-start\",\"pass\":\"ch_vect\",\"pass_number\":190,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":null}\n{\"seq\":259,\"event\":\"pass-end\",\"pass\":\"ch_vect\",\"pass_number\":190,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":0.000006000}\n{\"seq\":260,\"event\":\"pass-start\",\"pass\":\"ifcvt\",\"pass_number\":191,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":null}\n{\"seq\":261,\"event\":\"pass-end\",\"pass\":\"ifcvt\",\"pass_number\":191,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":0.000008000}\n{\"seq\":262,\"event\":\"pass-start\",\"pass\":\"vect\",\"pass_number\":192,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":null}\n{\"seq\":263,\"event\":\"pass-end\",\"pass\":\"vect\",\"pass_number\":192,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":0.000884000}\n{\"seq\":264,\"event\":\"pass-start\",\"pass\":\"dce\",\"pass_number\":193,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":null}\n{\"seq\":265,\"event\":\"pass-end\",\"pass\":\"dce\",\"pass_number\":193,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":0.000007000}\n{\"seq\":266,\"event\":\"pass-start\",\"pass\":\"pcom\",\"pass_number\":194,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":null}\n{\"seq\":267,\"event\":\"pass-end\",\"pass\":\"pcom\",\"pass_number\":194,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":0.000019000}\n{\"seq\":268,\"event\":\"pass-start\",\"pass\":\"cunroll\",\"pass_number\":195,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":null}\n{\"seq\":269,\"event\":\"pass-end\",\"pass\":\"cunroll\",\"pass_number\":195,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":0.000040000}\n{\"seq\":270,\"event\":\"pass-start\",\"pass\":\"slp\",\"pass_number\":198,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":null}\n{\"seq\":271,\"event\":\"pass-end\",\"pass\":\"slp\",\"pass_number\":198,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":0.000070000}\n{\"seq\":272,\"event\":\"pass-start\",\"pass\":\"ivopts\",\"pass_number\":200,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":null}\n{\"seq\":273,\"event\":\"pass-end\",\"pass\":\"ivopts\",\"pass_number\":200,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":0.000200000}\n{\"seq\":274,\"event\":\"pass-start\",\"pass\":\"lim\",\"pass_number\":201,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":null}\n{\"seq\":275,\"event\":\"pass-end\",\"pass\":\"lim\",\"pass_number\":201,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":0.000032000}\n{\"seq\":276,\"event\":\"pass-start\",\"pass\":\"loopdone\",\"pass_number\":202,\"function\":\"f\",\"properties\":1174847,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":null}\n{\"seq\":277,\"event\":\"pass-end\",\"pass\":\"loopdone\",\"pass_number\":202,\"function\":\"f\",\"properties\":1436991,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000078000}\n{\"seq\":278,\"event\":\"pass-start\",\"pass\":\"veclower2\",\"pass_number\":206,\"function\":\"f\",\"properties\":1436991,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":279,\"event\":\"pass-end\",\"pass\":\"veclower2\",\"pass_number\":206,\"function\":\"f\",\"properties\":1441087,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000012000}\n{\"seq\":280,\"event\":\"pass-start\",\"pass\":\"switchlower\",\"pass_number\":207,\"function\":\"f\",\"properties\":1441087,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":281,\"event\":\"pass-end\",\"pass\":\"switchlower\",\"pass_number\":207,\"function\":\"f\",\"properties\":1441087,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000012000}\n{\"seq\":282,\"event\":\"pass-start\",\"pass\":\"sincos\",\"pass_number\":208,\"function\":\"f\",\"properties\":1441087,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":283,\"event\":\"pass-end\",\"pass\":\"sincos\",\"pass_number\":208,\"function\":\"f\",\"properties\":1441087,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000006000}\n{\"seq\":284,\"event\":\"pass-start\",\"pass\":\"reassoc\",\"pass_number\":210,\"function\":\"f\",\"properties\":1441087,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":285,\"event\":\"pass-end\",\"pass\":\"reassoc\",\"pass_number\":210,\"function\":\"f\",\"properties\":1441087,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000005000}\n{\"seq\":286,\"event\":\"pass-start\",\"pass\":\"slsr\",\"pass_number\":211,\"function\":\"f\",\"properties\":1441087,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":287,\"event\":\"pass-end\",\"pass\":\"slsr\",\"pass_number\":211,\"function\":\"f\",\"properties\":1441087,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000013000}\n{\"seq\":288,\"event\":\"pass-start\",\"pass\":\"fre\",\"pass_number\":214,\"function\":\"f\",\"properties\":1441087,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":289,\"event\":\"pass-end\",\"pass\":\"fre\",\"pass_number\":214,\"function\":\"f\",\"properties\":1441087,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000023000}\n{\"seq\":290,\"event\":\"pass-start\",\"pass\":\"thread\",\"pass_number\":215,\"function\":\"f\",\"properties\":1441087,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":291,\"event\":\"pass-end\",\"pass\":\"thread\",\"pass_number\":215,\"function\":\"f\",\"properties\":1441087,\"statements\":5,\"insns\":null,\"blocks\":7,\"seconds\":0.000042000}\n{\"seq\":292,\"event\":\"pass-start\",\"pass\":\"dom\",\"pass_number\":216,\"function\":\"f\",\"properties\":1441087,\"statements\":5,\"insns\":null,\"blocks\":7,\"seconds\":null}\n{\"seq\":293,\"event\":\"pass-end\",\"pass\":\"dom\",\"pass_number\":216,\"function\":\"f\",\"properties\":1441087,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000158000}\n{\"seq\":294,\"event\":\"pass-start\",\"pass\":\"strlen\",\"pass_number\":217,\"function\":\"f\",\"properties\":1441087,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":295,\"event\":\"pass-end\",\"pass\":\"strlen\",\"pass_number\":217,\"function\":\"f\",\"properties\":1441087,\"statements\":5,\"insns\":null,\"blocks\":7,\"seconds\":0.000025000}\n{\"seq\":296,\"event\":\"pass-start\",\"pass\":\"threadfull\",\"pass_number\":218,\"function\":\"f\",\"properties\":1441087,\"statements\":5,\"insns\":null,\"blocks\":7,\"seconds\":null}\n{\"seq\":297,\"event\":\"pass-end\",\"pass\":\"threadfull\",\"pass_number\":218,\"function\":\"f\",\"properties\":1441087,\"statements\":5,\"insns\":null,\"blocks\":7,\"seconds\":0.000027000}\n{\"seq\":298,\"event\":\"pass-start\",\"pass\":\"vrp\",\"pass_number\":219,\"function\":\"f\",\"properties\":1441087,\"statements\":5,\"insns\":null,\"blocks\":7,\"seconds\":null}\n{\"seq\":299,\"event\":\"pass-end\",\"pass\":\"vrp\",\"pass_number\":219,\"function\":\"f\",\"properties\":1441087,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000168000}\n{\"seq\":300,\"event\":\"pass-start\",\"pass\":\"ccp\",\"pass_number\":220,\"function\":\"f\",\"properties\":1441087,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":301,\"event\":\"pass-end\",\"pass\":\"ccp\",\"pass_number\":220,\"function\":\"f\",\"properties\":1441087,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000077000}\n{\"seq\":302,\"event\":\"pass-start\",\"pass\":\"wrestrict\",\"pass_number\":221,\"function\":\"f\",\"properties\":1441087,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":303,\"event\":\"pass-end\",\"pass\":\"wrestrict\",\"pass_number\":221,\"function\":\"f\",\"properties\":1441087,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000011000}\n{\"seq\":304,\"event\":\"pass-start\",\"pass\":\"dse\",\"pass_number\":222,\"function\":\"f\",\"properties\":1441087,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":305,\"event\":\"pass-end\",\"pass\":\"dse\",\"pass_number\":222,\"function\":\"f\",\"properties\":1441087,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000004000}\n{\"seq\":306,\"event\":\"pass-start\",\"pass\":\"dce\",\"pass_number\":223,\"function\":\"f\",\"properties\":1441087,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":307,\"event\":\"pass-end\",\"pass\":\"dce\",\"pass_number\":223,\"function\":\"f\",\"properties\":1441087,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000008000}\n{\"seq\":308,\"event\":\"pass-start\",\"pass\":\"forwprop\",\"pass_number\":224,\"function\":\"f\",\"properties\":1441087,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":309,\"event\":\"pass-end\",\"pass\":\"forwprop\",\"pass_number\":224,\"function\":\"f\",\"properties\":3538239,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000016000}\n{\"seq\":310,\"event\":\"pass-start\",\"pass\":\"sink\",\"pass_number\":225,\"function\":\"f\",\"properties\":3538239,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":311,\"event\":\"pass-end\",\"pass\":\"sink\",\"pass_number\":225,\"function\":\"f\",\"properties\":3538239,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000025000}\n{\"seq\":312,\"event\":\"pass-start\",\"pass\":\"phiopt\",\"pass_number\":226,\"function\":\"f\",\"properties\":3538239,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":313,\"event\":\"pass-end\",\"pass\":\"phiopt\",\"pass_number\":226,\"function\":\"f\",\"properties\":3538239,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000003000}\n{\"seq\":314,\"event\":\"pass-start\",\"pass\":\"widening_mul\",\"pass_number\":227,\"function\":\"f\",\"properties\":3538239,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":315,\"event\":\"pass-end\",\"pass\":\"widening_mul\",\"pass_number\":227,\"function\":\"f\",\"properties\":3538239,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000041000}\n{\"seq\":316,\"event\":\"pass-start\",\"pass\":\"store-merging\",\"pass_number\":228,\"function\":\"f\",\"properties\":3538239,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":317,\"event\":\"pass-end\",\"pass\":\"store-merging\",\"pass_number\":228,\"function\":\"f\",\"properties\":3538239,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000008000}\n{\"seq\":318,\"event\":\"pass-start\",\"pass\":\"cddce\",\"pass_number\":229,\"function\":\"f\",\"properties\":3538239,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":319,\"event\":\"pass-end\",\"pass\":\"cddce\",\"pass_number\":229,\"function\":\"f\",\"properties\":3538239,\"statements\":5,\"insns\":null,\"blocks\":7,\"seconds\":0.000011000}\n{\"seq\":320,\"event\":\"pass-start\",\"pass\":\"sccopy\",\"pass_number\":230,\"function\":\"f\",\"properties\":3538239,\"statements\":5,\"insns\":null,\"blocks\":7,\"seconds\":null}\n{\"seq\":321,\"event\":\"pass-end\",\"pass\":\"sccopy\",\"pass_number\":230,\"function\":\"f\",\"properties\":3538239,\"statements\":5,\"insns\":null,\"blocks\":7,\"seconds\":0.000005000}\n{\"seq\":322,\"event\":\"pass-start\",\"pass\":\"tailc\",\"pass_number\":231,\"function\":\"f\",\"properties\":3538239,\"statements\":5,\"insns\":null,\"blocks\":7,\"seconds\":null}\n{\"seq\":323,\"event\":\"pass-end\",\"pass\":\"tailc\",\"pass_number\":231,\"function\":\"f\",\"properties\":3538239,\"statements\":5,\"insns\":null,\"blocks\":7,\"seconds\":0.000001000}\n{\"seq\":324,\"event\":\"pass-start\",\"pass\":\"crited\",\"pass_number\":232,\"function\":\"f\",\"properties\":3538239,\"statements\":5,\"insns\":null,\"blocks\":7,\"seconds\":null}\n{\"seq\":325,\"event\":\"pass-end\",\"pass\":\"crited\",\"pass_number\":232,\"function\":\"f\",\"properties\":3538303,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":0.000003000}\n{\"seq\":326,\"event\":\"pass-start\",\"pass\":\"local-pure-const\",\"pass_number\":234,\"function\":\"f\",\"properties\":3538303,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":null}\n{\"seq\":327,\"event\":\"pass-end\",\"pass\":\"local-pure-const\",\"pass_number\":234,\"function\":\"f\",\"properties\":3538303,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":0.000006000}\n{\"seq\":328,\"event\":\"pass-start\",\"pass\":\"modref\",\"pass_number\":235,\"function\":\"f\",\"properties\":3538303,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":null}\n{\"seq\":329,\"event\":\"pass-end\",\"pass\":\"modref\",\"pass_number\":235,\"function\":\"f\",\"properties\":3538303,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":0.000011000}\n{\"seq\":330,\"event\":\"pass-start\",\"pass\":\"uncprop\",\"pass_number\":236,\"function\":\"f\",\"properties\":3538303,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":null}\n{\"seq\":331,\"event\":\"pass-end\",\"pass\":\"uncprop\",\"pass_number\":236,\"function\":\"f\",\"properties\":3538303,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":0.000021000}\n{\"seq\":332,\"event\":\"pass-start\",\"pass\":\"nrv\",\"pass_number\":272,\"function\":\"f\",\"properties\":3538303,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":null}\n{\"seq\":333,\"event\":\"pass-end\",\"pass\":\"nrv\",\"pass_number\":272,\"function\":\"f\",\"properties\":3538303,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":0.000005000}\n{\"seq\":334,\"event\":\"pass-start\",\"pass\":\"isel\",\"pass_number\":273,\"function\":\"f\",\"properties\":3538303,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":null}\n{\"seq\":335,\"event\":\"pass-end\",\"pass\":\"isel\",\"pass_number\":273,\"function\":\"f\",\"properties\":3538303,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":0.000002000}\n{\"seq\":336,\"event\":\"pass-start\",\"pass\":\"waccess\",\"pass_number\":276,\"function\":\"f\",\"properties\":3538303,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":null}\n{\"seq\":337,\"event\":\"pass-end\",\"pass\":\"waccess\",\"pass_number\":276,\"function\":\"f\",\"properties\":3538303,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":0.000010000}\n{\"seq\":338,\"event\":\"pass-start\",\"pass\":\"optimized\",\"pass_number\":277,\"function\":\"f\",\"properties\":3538303,\"statements\":5,\"insns\":null,\"blocks\":9,\"seconds\":null}\n{\"seq\":339,\"event\":\"pass-end\",\"pass\":\"optimized\",\"pass_number\":277,\"function\":\"f\",\"properties\":3538303,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":0.000017000}\n{\"seq\":340,\"event\":\"pass-start\",\"pass\":\"expand\",\"pass_number\":278,\"function\":\"f\",\"properties\":3538303,\"statements\":5,\"insns\":null,\"blocks\":5,\"seconds\":null}\n{\"seq\":341,\"event\":\"pass-end\",\"pass\":\"expand\",\"pass_number\":278,\"function\":\"f\",\"properties\":3538136,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":0.000243000}\n{\"seq\":342,\"event\":\"pass-start\",\"pass\":\"*rest_of_compilation\",\"pass_number\":-1,\"function\":\"f\",\"properties\":3538136,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":null}\n{\"seq\":343,\"event\":\"pass-end\",\"pass\":\"*rest_of_compilation\",\"pass_number\":-1,\"function\":\"f\",\"properties\":3538136,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":0.000000000}\n{\"seq\":344,\"event\":\"pass-start\",\"pass\":\"vregs\",\"pass_number\":279,\"function\":\"f\",\"properties\":3538136,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":null}\n{\"seq\":345,\"event\":\"pass-end\",\"pass\":\"vregs\",\"pass_number\":279,\"function\":\"f\",\"properties\":3538136,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":0.000153000}\n{\"seq\":346,\"event\":\"pass-start\",\"pass\":\"into_cfglayout\",\"pass_number\":280,\"function\":\"f\",\"properties\":3538136,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":null}\n{\"seq\":347,\"event\":\"pass-end\",\"pass\":\"into_cfglayout\",\"pass_number\":280,\"function\":\"f\",\"properties\":3538648,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":0.000010000}\n{\"seq\":348,\"event\":\"pass-start\",\"pass\":\"jump\",\"pass_number\":281,\"function\":\"f\",\"properties\":3538648,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":null}\n{\"seq\":349,\"event\":\"pass-end\",\"pass\":\"jump\",\"pass_number\":281,\"function\":\"f\",\"properties\":3538648,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":0.000031000}\n{\"seq\":350,\"event\":\"pass-start\",\"pass\":\"subreg1\",\"pass_number\":282,\"function\":\"f\",\"properties\":3538648,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":null}\n{\"seq\":351,\"event\":\"pass-end\",\"pass\":\"subreg1\",\"pass_number\":282,\"function\":\"f\",\"properties\":3538648,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":0.000001000}\n{\"seq\":352,\"event\":\"pass-start\",\"pass\":\"dfinit\",\"pass_number\":283,\"function\":\"f\",\"properties\":3538648,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":null}\n{\"seq\":353,\"event\":\"pass-end\",\"pass\":\"dfinit\",\"pass_number\":283,\"function\":\"f\",\"properties\":3538648,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":0.000033000}\n{\"seq\":354,\"event\":\"pass-start\",\"pass\":\"cse1\",\"pass_number\":284,\"function\":\"f\",\"properties\":3538648,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":null}\n{\"seq\":355,\"event\":\"pass-end\",\"pass\":\"cse1\",\"pass_number\":284,\"function\":\"f\",\"properties\":3538648,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":0.000144000}\n{\"seq\":356,\"event\":\"pass-start\",\"pass\":\"fwprop1\",\"pass_number\":285,\"function\":\"f\",\"properties\":3538648,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":null}\n{\"seq\":357,\"event\":\"pass-end\",\"pass\":\"fwprop1\",\"pass_number\":285,\"function\":\"f\",\"properties\":3538648,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":0.000155000}\n{\"seq\":358,\"event\":\"pass-start\",\"pass\":\"cprop\",\"pass_number\":286,\"function\":\"f\",\"properties\":3538648,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":null}\n{\"seq\":359,\"event\":\"pass-end\",\"pass\":\"cprop\",\"pass_number\":286,\"function\":\"f\",\"properties\":3538648,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":0.000041000}\n{\"seq\":360,\"event\":\"pass-start\",\"pass\":\"rtl pre\",\"pass_number\":287,\"function\":\"f\",\"properties\":3538648,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":null}\n{\"seq\":361,\"event\":\"pass-end\",\"pass\":\"rtl pre\",\"pass_number\":287,\"function\":\"f\",\"properties\":3538648,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":0.000021000}\n{\"seq\":362,\"event\":\"pass-start\",\"pass\":\"hardreg_pre\",\"pass_number\":289,\"function\":\"f\",\"properties\":3538648,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":null}\n{\"seq\":363,\"event\":\"pass-end\",\"pass\":\"hardreg_pre\",\"pass_number\":289,\"function\":\"f\",\"properties\":3538648,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":0.000001000}\n{\"seq\":364,\"event\":\"pass-start\",\"pass\":\"cprop\",\"pass_number\":290,\"function\":\"f\",\"properties\":3538648,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":null}\n{\"seq\":365,\"event\":\"pass-end\",\"pass\":\"cprop\",\"pass_number\":290,\"function\":\"f\",\"properties\":3538648,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":0.000061000}\n{\"seq\":366,\"event\":\"pass-start\",\"pass\":\"ce1\",\"pass_number\":293,\"function\":\"f\",\"properties\":3538648,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":null}\n{\"seq\":367,\"event\":\"pass-end\",\"pass\":\"ce1\",\"pass_number\":293,\"function\":\"f\",\"properties\":3538648,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":0.000052000}\n{\"seq\":368,\"event\":\"pass-start\",\"pass\":\"reginfo\",\"pass_number\":294,\"function\":\"f\",\"properties\":3538648,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":null}\n{\"seq\":369,\"event\":\"pass-end\",\"pass\":\"reginfo\",\"pass_number\":294,\"function\":\"f\",\"properties\":3538648,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":0.000006000}\n{\"seq\":370,\"event\":\"pass-start\",\"pass\":\"loop2\",\"pass_number\":295,\"function\":\"f\",\"properties\":3538648,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":null}\n{\"seq\":371,\"event\":\"pass-end\",\"pass\":\"loop2\",\"pass_number\":295,\"function\":\"f\",\"properties\":3538648,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":0.000000000}\n{\"seq\":372,\"event\":\"pass-start\",\"pass\":\"loop2_init\",\"pass_number\":296,\"function\":\"f\",\"properties\":3538648,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":null}\n{\"seq\":373,\"event\":\"pass-end\",\"pass\":\"loop2_init\",\"pass_number\":296,\"function\":\"f\",\"properties\":3538648,\"statements\":null,\"insns\":23,\"blocks\":8,\"seconds\":0.000017000}\n{\"seq\":374,\"event\":\"pass-start\",\"pass\":\"loop2_invariant\",\"pass_number\":297,\"function\":\"f\",\"properties\":3538648,\"statements\":null,\"insns\":23,\"blocks\":8,\"seconds\":null}\n{\"seq\":375,\"event\":\"pass-end\",\"pass\":\"loop2_invariant\",\"pass_number\":297,\"function\":\"f\",\"properties\":3538648,\"statements\":null,\"insns\":23,\"blocks\":8,\"seconds\":0.000043000}\n{\"seq\":376,\"event\":\"pass-start\",\"pass\":\"loop2_done\",\"pass_number\":300,\"function\":\"f\",\"properties\":3538648,\"statements\":null,\"insns\":23,\"blocks\":8,\"seconds\":null}\n{\"seq\":377,\"event\":\"pass-end\",\"pass\":\"loop2_done\",\"pass_number\":300,\"function\":\"f\",\"properties\":3536600,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":0.000016000}\n{\"seq\":378,\"event\":\"pass-start\",\"pass\":\"cprop\",\"pass_number\":303,\"function\":\"f\",\"properties\":3536600,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":null}\n{\"seq\":379,\"event\":\"pass-end\",\"pass\":\"cprop\",\"pass_number\":303,\"function\":\"f\",\"properties\":3536600,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":0.000038000}\n{\"seq\":380,\"event\":\"pass-start\",\"pass\":\"cse2\",\"pass_number\":304,\"function\":\"f\",\"properties\":3536600,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":null}\n{\"seq\":381,\"event\":\"pass-end\",\"pass\":\"cse2\",\"pass_number\":304,\"function\":\"f\",\"properties\":3536600,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":0.000062000}\n{\"seq\":382,\"event\":\"pass-start\",\"pass\":\"dse1\",\"pass_number\":305,\"function\":\"f\",\"properties\":3536600,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":null}\n{\"seq\":383,\"event\":\"pass-end\",\"pass\":\"dse1\",\"pass_number\":305,\"function\":\"f\",\"properties\":3536600,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":0.000041000}\n{\"seq\":384,\"event\":\"pass-start\",\"pass\":\"fwprop2\",\"pass_number\":306,\"function\":\"f\",\"properties\":3536600,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":null}\n{\"seq\":385,\"event\":\"pass-end\",\"pass\":\"fwprop2\",\"pass_number\":306,\"function\":\"f\",\"properties\":3536600,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":0.000033000}\n{\"seq\":386,\"event\":\"pass-start\",\"pass\":\"auto_inc_dec\",\"pass_number\":307,\"function\":\"f\",\"properties\":3536600,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":null}\n{\"seq\":387,\"event\":\"pass-end\",\"pass\":\"auto_inc_dec\",\"pass_number\":307,\"function\":\"f\",\"properties\":3536600,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":0.000005000}\n{\"seq\":388,\"event\":\"pass-start\",\"pass\":\"init-regs\",\"pass_number\":308,\"function\":\"f\",\"properties\":3536600,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":null}\n{\"seq\":389,\"event\":\"pass-end\",\"pass\":\"init-regs\",\"pass_number\":308,\"function\":\"f\",\"properties\":3536600,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":0.000001000}\n{\"seq\":390,\"event\":\"pass-start\",\"pass\":\"ud_dce\",\"pass_number\":309,\"function\":\"f\",\"properties\":3536600,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":null}\n{\"seq\":391,\"event\":\"pass-end\",\"pass\":\"ud_dce\",\"pass_number\":309,\"function\":\"f\",\"properties\":3536600,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":0.000014000}\n{\"seq\":392,\"event\":\"pass-start\",\"pass\":\"ext_dce\",\"pass_number\":310,\"function\":\"f\",\"properties\":3536600,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":null}\n{\"seq\":393,\"event\":\"pass-end\",\"pass\":\"ext_dce\",\"pass_number\":310,\"function\":\"f\",\"properties\":3536600,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":0.000028000}\n{\"seq\":394,\"event\":\"pass-start\",\"pass\":\"combine\",\"pass_number\":311,\"function\":\"f\",\"properties\":3536600,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":null}\n{\"seq\":395,\"event\":\"pass-end\",\"pass\":\"combine\",\"pass_number\":311,\"function\":\"f\",\"properties\":3536600,\"statements\":null,\"insns\":22,\"blocks\":7,\"seconds\":0.000156000}\n{\"seq\":396,\"event\":\"pass-start\",\"pass\":\"late_combine\",\"pass_number\":312,\"function\":\"f\",\"properties\":3536600,\"statements\":null,\"insns\":22,\"blocks\":7,\"seconds\":null}\n{\"seq\":397,\"event\":\"pass-end\",\"pass\":\"late_combine\",\"pass_number\":312,\"function\":\"f\",\"properties\":3536600,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":0.000076000}\n{\"seq\":398,\"event\":\"pass-start\",\"pass\":\"ce2\",\"pass_number\":313,\"function\":\"f\",\"properties\":3536600,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":null}\n{\"seq\":399,\"event\":\"pass-end\",\"pass\":\"ce2\",\"pass_number\":313,\"function\":\"f\",\"properties\":3536600,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":0.000014000}\n{\"seq\":400,\"event\":\"pass-start\",\"pass\":\"jump_after_combine\",\"pass_number\":314,\"function\":\"f\",\"properties\":3536600,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":null}\n{\"seq\":401,\"event\":\"pass-end\",\"pass\":\"jump_after_combine\",\"pass_number\":314,\"function\":\"f\",\"properties\":3536600,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":0.000010000}\n{\"seq\":402,\"event\":\"pass-start\",\"pass\":\"outof_cfglayout\",\"pass_number\":316,\"function\":\"f\",\"properties\":3536600,\"statements\":null,\"insns\":21,\"blocks\":7,\"seconds\":null}\n{\"seq\":403,\"event\":\"pass-end\",\"pass\":\"outof_cfglayout\",\"pass_number\":316,\"function\":\"f\",\"properties\":3536088,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":0.000011000}\n{\"seq\":404,\"event\":\"pass-start\",\"pass\":\"split1\",\"pass_number\":317,\"function\":\"f\",\"properties\":3536088,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":null}\n{\"seq\":405,\"event\":\"pass-end\",\"pass\":\"split1\",\"pass_number\":317,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":0.000031000}\n{\"seq\":406,\"event\":\"pass-start\",\"pass\":\"subreg3\",\"pass_number\":318,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":null}\n{\"seq\":407,\"event\":\"pass-end\",\"pass\":\"subreg3\",\"pass_number\":318,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":0.000002000}\n{\"seq\":408,\"event\":\"pass-start\",\"pass\":\"*stack_ptr_mod\",\"pass_number\":-1,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":null}\n{\"seq\":409,\"event\":\"pass-end\",\"pass\":\"*stack_ptr_mod\",\"pass_number\":-1,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":0.000002000}\n{\"seq\":410,\"event\":\"pass-start\",\"pass\":\"mode_sw\",\"pass_number\":320,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":null}\n{\"seq\":411,\"event\":\"pass-end\",\"pass\":\"mode_sw\",\"pass_number\":320,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":0.000001000}\n{\"seq\":412,\"event\":\"pass-start\",\"pass\":\"asmcons\",\"pass_number\":321,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":null}\n{\"seq\":413,\"event\":\"pass-end\",\"pass\":\"asmcons\",\"pass_number\":321,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":0.000007000}\n{\"seq\":414,\"event\":\"pass-start\",\"pass\":\"early_ra\",\"pass_number\":324,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":null}\n{\"seq\":415,\"event\":\"pass-end\",\"pass\":\"early_ra\",\"pass_number\":324,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":0.000022000}\n{\"seq\":416,\"event\":\"pass-start\",\"pass\":\"ldp_fusion\",\"pass_number\":327,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":null}\n{\"seq\":417,\"event\":\"pass-end\",\"pass\":\"ldp_fusion\",\"pass_number\":327,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":0.000037000}\n{\"seq\":418,\"event\":\"pass-start\",\"pass\":\"early_remat\",\"pass_number\":328,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":null}\n{\"seq\":419,\"event\":\"pass-end\",\"pass\":\"early_remat\",\"pass_number\":328,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":0.000003000}\n{\"seq\":420,\"event\":\"pass-start\",\"pass\":\"dep_fusion\",\"pass_number\":329,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":null}\n{\"seq\":421,\"event\":\"pass-end\",\"pass\":\"dep_fusion\",\"pass_number\":329,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":0.000017000}\n{\"seq\":422,\"event\":\"pass-start\",\"pass\":\"ira\",\"pass_number\":330,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":null}\n{\"seq\":423,\"event\":\"pass-end\",\"pass\":\"ira\",\"pass_number\":330,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":0.002013000}\n{\"seq\":424,\"event\":\"pass-start\",\"pass\":\"reload\",\"pass_number\":331,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":null}\n{\"seq\":425,\"event\":\"pass-end\",\"pass\":\"reload\",\"pass_number\":331,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":0.000180000}\n{\"seq\":426,\"event\":\"pass-start\",\"pass\":\"*all-postreload\",\"pass_number\":-1,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":null}\n{\"seq\":427,\"event\":\"pass-end\",\"pass\":\"*all-postreload\",\"pass_number\":-1,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":0.000001000}\n{\"seq\":428,\"event\":\"pass-start\",\"pass\":\"postreload\",\"pass_number\":332,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":null}\n{\"seq\":429,\"event\":\"pass-end\",\"pass\":\"postreload\",\"pass_number\":332,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":0.000067000}\n{\"seq\":430,\"event\":\"pass-start\",\"pass\":\"late_combine\",\"pass_number\":333,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":null}\n{\"seq\":431,\"event\":\"pass-end\",\"pass\":\"late_combine\",\"pass_number\":333,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":0.000065000}\n{\"seq\":432,\"event\":\"pass-start\",\"pass\":\"split2\",\"pass_number\":335,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":null}\n{\"seq\":433,\"event\":\"pass-end\",\"pass\":\"split2\",\"pass_number\":335,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":0.000005000}\n{\"seq\":434,\"event\":\"pass-start\",\"pass\":\"ree\",\"pass_number\":336,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":null}\n{\"seq\":435,\"event\":\"pass-end\",\"pass\":\"ree\",\"pass_number\":336,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":0.000024000}\n{\"seq\":436,\"event\":\"pass-start\",\"pass\":\"cmpelim\",\"pass_number\":337,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":null}\n{\"seq\":437,\"event\":\"pass-end\",\"pass\":\"cmpelim\",\"pass_number\":337,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":0.000012000}\n{\"seq\":438,\"event\":\"pass-start\",\"pass\":\"pro_and_epilogue\",\"pass_number\":338,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":25,\"blocks\":8,\"seconds\":null}\n{\"seq\":439,\"event\":\"pass-end\",\"pass\":\"pro_and_epilogue\",\"pass_number\":338,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":30,\"blocks\":8,\"seconds\":0.000068000}\n{\"seq\":440,\"event\":\"pass-start\",\"pass\":\"dse2\",\"pass_number\":339,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":30,\"blocks\":8,\"seconds\":null}\n{\"seq\":441,\"event\":\"pass-end\",\"pass\":\"dse2\",\"pass_number\":339,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":30,\"blocks\":8,\"seconds\":0.000025000}\n{\"seq\":442,\"event\":\"pass-start\",\"pass\":\"jump2\",\"pass_number\":341,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":30,\"blocks\":8,\"seconds\":null}\n{\"seq\":443,\"event\":\"pass-end\",\"pass\":\"jump2\",\"pass_number\":341,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":30,\"blocks\":8,\"seconds\":0.000007000}\n{\"seq\":444,\"event\":\"pass-start\",\"pass\":\"compgotos\",\"pass_number\":342,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":30,\"blocks\":8,\"seconds\":null}\n{\"seq\":445,\"event\":\"pass-end\",\"pass\":\"compgotos\",\"pass_number\":342,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":30,\"blocks\":8,\"seconds\":0.000004000}\n{\"seq\":446,\"event\":\"pass-start\",\"pass\":\"sched_fusion\",\"pass_number\":343,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":30,\"blocks\":8,\"seconds\":null}\n{\"seq\":447,\"event\":\"pass-end\",\"pass\":\"sched_fusion\",\"pass_number\":343,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":30,\"blocks\":8,\"seconds\":0.000219000}\n{\"seq\":448,\"event\":\"pass-start\",\"pass\":\"ldp_fusion\",\"pass_number\":344,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":30,\"blocks\":8,\"seconds\":null}\n{\"seq\":449,\"event\":\"pass-end\",\"pass\":\"ldp_fusion\",\"pass_number\":344,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":30,\"blocks\":8,\"seconds\":0.000030000}\n{\"seq\":450,\"event\":\"pass-start\",\"pass\":\"peephole2\",\"pass_number\":345,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":30,\"blocks\":8,\"seconds\":null}\n{\"seq\":451,\"event\":\"pass-end\",\"pass\":\"peephole2\",\"pass_number\":345,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":30,\"blocks\":8,\"seconds\":0.000025000}\n{\"seq\":452,\"event\":\"pass-start\",\"pass\":\"ce3\",\"pass_number\":346,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":30,\"blocks\":8,\"seconds\":null}\n{\"seq\":453,\"event\":\"pass-end\",\"pass\":\"ce3\",\"pass_number\":346,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":30,\"blocks\":8,\"seconds\":0.000009000}\n{\"seq\":454,\"event\":\"pass-start\",\"pass\":\"dep_fusion\",\"pass_number\":347,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":30,\"blocks\":8,\"seconds\":null}\n{\"seq\":455,\"event\":\"pass-end\",\"pass\":\"dep_fusion\",\"pass_number\":347,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":30,\"blocks\":8,\"seconds\":0.000032000}\n{\"seq\":456,\"event\":\"pass-start\",\"pass\":\"fold_mem_offsets\",\"pass_number\":350,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":30,\"blocks\":8,\"seconds\":null}\n{\"seq\":457,\"event\":\"pass-end\",\"pass\":\"fold_mem_offsets\",\"pass_number\":350,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":30,\"blocks\":8,\"seconds\":0.000015000}\n{\"seq\":458,\"event\":\"pass-start\",\"pass\":\"cprop_hardreg\",\"pass_number\":351,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":30,\"blocks\":8,\"seconds\":null}\n{\"seq\":459,\"event\":\"pass-end\",\"pass\":\"cprop_hardreg\",\"pass_number\":351,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":30,\"blocks\":8,\"seconds\":0.000028000}\n{\"seq\":460,\"event\":\"pass-start\",\"pass\":\"rtl_dce\",\"pass_number\":352,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":30,\"blocks\":8,\"seconds\":null}\n{\"seq\":461,\"event\":\"pass-end\",\"pass\":\"rtl_dce\",\"pass_number\":352,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":30,\"blocks\":8,\"seconds\":0.000005000}\n{\"seq\":462,\"event\":\"pass-start\",\"pass\":\"bbro\",\"pass_number\":354,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":30,\"blocks\":8,\"seconds\":null}\n{\"seq\":463,\"event\":\"pass-end\",\"pass\":\"bbro\",\"pass_number\":354,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":31,\"blocks\":7,\"seconds\":0.000041000}\n{\"seq\":464,\"event\":\"pass-start\",\"pass\":\"*leaf_regs\",\"pass_number\":-1,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":31,\"blocks\":7,\"seconds\":null}\n{\"seq\":465,\"event\":\"pass-end\",\"pass\":\"*leaf_regs\",\"pass_number\":-1,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":31,\"blocks\":7,\"seconds\":0.000001000}\n{\"seq\":466,\"event\":\"pass-start\",\"pass\":\"split3\",\"pass_number\":355,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":31,\"blocks\":7,\"seconds\":null}\n{\"seq\":467,\"event\":\"pass-end\",\"pass\":\"split3\",\"pass_number\":355,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":31,\"blocks\":7,\"seconds\":0.000003000}\n{\"seq\":468,\"event\":\"pass-start\",\"pass\":\"sched2\",\"pass_number\":356,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":31,\"blocks\":7,\"seconds\":null}\n{\"seq\":469,\"event\":\"pass-end\",\"pass\":\"sched2\",\"pass_number\":356,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":31,\"blocks\":7,\"seconds\":0.000072000}\n{\"seq\":470,\"event\":\"pass-start\",\"pass\":\"*all-late_compilation\",\"pass_number\":-1,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":31,\"blocks\":7,\"seconds\":null}\n{\"seq\":471,\"event\":\"pass-end\",\"pass\":\"*all-late_compilation\",\"pass_number\":-1,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":31,\"blocks\":7,\"seconds\":0.000000000}\n{\"seq\":472,\"event\":\"pass-start\",\"pass\":\"zero_call_used_regs\",\"pass_number\":362,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":31,\"blocks\":7,\"seconds\":null}\n{\"seq\":473,\"event\":\"pass-end\",\"pass\":\"zero_call_used_regs\",\"pass_number\":362,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":31,\"blocks\":7,\"seconds\":0.000000000}\n{\"seq\":474,\"event\":\"pass-start\",\"pass\":\"alignments\",\"pass_number\":363,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":31,\"blocks\":7,\"seconds\":null}\n{\"seq\":475,\"event\":\"pass-end\",\"pass\":\"alignments\",\"pass_number\":363,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":31,\"blocks\":7,\"seconds\":0.000012000}\n{\"seq\":476,\"event\":\"pass-start\",\"pass\":\"*free_cfg\",\"pass_number\":-1,\"function\":\"f\",\"properties\":3667160,\"statements\":null,\"insns\":31,\"blocks\":7,\"seconds\":null}\n{\"seq\":477,\"event\":\"pass-end\",\"pass\":\"*free_cfg\",\"pass_number\":-1,\"function\":\"f\",\"properties\":3667152,\"statements\":null,\"insns\":31,\"blocks\":7,\"seconds\":0.000001000}\n{\"seq\":478,\"event\":\"pass-start\",\"pass\":\"barriers\",\"pass_number\":367,\"function\":\"f\",\"properties\":3667152,\"statements\":null,\"insns\":31,\"blocks\":7,\"seconds\":null}\n{\"seq\":479,\"event\":\"pass-end\",\"pass\":\"barriers\",\"pass_number\":367,\"function\":\"f\",\"properties\":3667152,\"statements\":null,\"insns\":31,\"blocks\":7,\"seconds\":0.000001000}\n{\"seq\":480,\"event\":\"pass-start\",\"pass\":\"split5\",\"pass_number\":369,\"function\":\"f\",\"properties\":3667152,\"statements\":null,\"insns\":31,\"blocks\":7,\"seconds\":null}\n{\"seq\":481,\"event\":\"pass-end\",\"pass\":\"split5\",\"pass_number\":369,\"function\":\"f\",\"properties\":3667152,\"statements\":null,\"insns\":31,\"blocks\":7,\"seconds\":0.000002000}\n{\"seq\":482,\"event\":\"pass-start\",\"pass\":\"shorten\",\"pass_number\":372,\"function\":\"f\",\"properties\":3667152,\"statements\":null,\"insns\":31,\"blocks\":7,\"seconds\":null}\n{\"seq\":483,\"event\":\"pass-end\",\"pass\":\"shorten\",\"pass_number\":372,\"function\":\"f\",\"properties\":3667152,\"statements\":null,\"insns\":31,\"blocks\":7,\"seconds\":0.000012000}\n{\"seq\":484,\"event\":\"pass-start\",\"pass\":\"nothrow\",\"pass_number\":373,\"function\":\"f\",\"properties\":3667152,\"statements\":null,\"insns\":31,\"blocks\":7,\"seconds\":null}\n{\"seq\":485,\"event\":\"pass-end\",\"pass\":\"nothrow\",\"pass_number\":373,\"function\":\"f\",\"properties\":3667152,\"statements\":null,\"insns\":31,\"blocks\":7,\"seconds\":0.000001000}\n{\"seq\":486,\"event\":\"pass-start\",\"pass\":\"dwarf2\",\"pass_number\":374,\"function\":\"f\",\"properties\":3667152,\"statements\":null,\"insns\":31,\"blocks\":7,\"seconds\":null}\n{\"seq\":487,\"event\":\"pass-end\",\"pass\":\"dwarf2\",\"pass_number\":374,\"function\":\"f\",\"properties\":3667152,\"statements\":null,\"insns\":31,\"blocks\":7,\"seconds\":0.000015000}\n{\"seq\":488,\"event\":\"pass-start\",\"pass\":\"final\",\"pass_number\":375,\"function\":\"f\",\"properties\":3667152,\"statements\":null,\"insns\":31,\"blocks\":7,\"seconds\":null}\n{\"seq\":489,\"event\":\"pass-end\",\"pass\":\"final\",\"pass_number\":375,\"function\":\"f\",\"properties\":3667152,\"statements\":null,\"insns\":31,\"blocks\":7,\"seconds\":0.000086000}\n{\"seq\":490,\"event\":\"pass-start\",\"pass\":\"dfinish\",\"pass_number\":376,\"function\":\"f\",\"properties\":3667152,\"statements\":null,\"insns\":31,\"blocks\":7,\"seconds\":null}\n{\"seq\":491,\"event\":\"pass-end\",\"pass\":\"dfinish\",\"pass_number\":376,\"function\":\"f\",\"properties\":3667152,\"statements\":null,\"insns\":31,\"blocks\":7,\"seconds\":0.000005000}\n{\"seq\":492,\"event\":\"pass-start\",\"pass\":\"*clean_state\",\"pass_number\":-1,\"function\":\"f\",\"properties\":3667152,\"statements\":null,\"insns\":31,\"blocks\":7,\"seconds\":null}\n{\"seq\":493,\"event\":\"pass-end\",\"pass\":\"*clean_state\",\"pass_number\":-1,\"function\":\"f\",\"properties\":3667024,\"statements\":null,\"insns\":null,\"blocks\":null,\"seconds\":0.000004000}\n", + "runs": { + "plain": { + "about": "corpora/programs/l2.c, no plugin at all", + "argv": [ + "gcc-16", + "-O2", + "-S", + "corpora/programs/l2.c", + "-o", + "b05-scratch.s" + ], + "returncode": 0, + "stdout": "", + "stderr": "", + "asm": "\t.arch armv8.5-a\n\t.build_version macos, 15, 0\n\t.text\n\t.align\t2\n\t.p2align 5,,15\n\t.globl _nearest\n_nearest:\nLFB1:\n\tcmp\tw1, 1\n\tble\tL5\n\tldp\tw7, w3, [x0]\n\tasr\tx8, x2, 32\n\tadd\tx5, x0, 8\n\tmov\tw6, 1\n\tmov\tw0, 0\n\tsub\tw3, w3, w8\n\tsub\tw7, w7, w2\n\tmul\tw3, w3, w3\n\tmadd\tw7, w7, w7, w3\n\t.p2align 5,,15\nL4:\n\tldp\tw4, w3, [x5], 8\n\tsub\tw4, w4, w2\n\tsub\tw3, w3, w8\n\tmul\tw4, w4, w4\n\tmadd\tw3, w3, w3, w4\n\tcmp\tw7, w3\n\tcsel\tw0, w0, w6, le\n\tadd\tw6, w6, 1\n\tcsel\tw7, w7, w3, le\n\tcmp\tw1, w6\n\tbne\tL4\n\tret\n\t.p2align 2,,3\nL5:\n\tmov\tw0, 0\n\tret\nLFE1:\n\t.section __TEXT,__eh_frame,coalesced,no_toc+strip_static_syms+live_support\nEH_frame1:\n\t.set L$set$0,LECIE1-LSCIE1\n\t.long L$set$0\nLSCIE1:\n\t.long\t0\n\t.byte\t0x3\n\t.ascii \"zR\\0\"\n\t.uleb128 0x1\n\t.sleb128 -8\n\t.uleb128 0x1e\n\t.uleb128 0x1\n\t.byte\t0x10\n\t.byte\t0xc\n\t.uleb128 0x1f\n\t.uleb128 0\n\t.align\t3\nLECIE1:\nLSFDE1:\n\t.set L$set$1,LEFDE1-LASFDE1\n\t.long L$set$1\nLASFDE1:\n\t.long\tLASFDE1-EH_frame1\n\t.quad\tLFB1-.\n\t.set L$set$2,LFE1-LFB1\n\t.quad L$set$2\n\t.uleb128 0\n\t.align\t3\nLEFDE1:\n\t.ident\t\"GCC: (Homebrew GCC 16.2.0) 16.2.0\"\n\t.subsections_via_symbols\n" + }, + "hello": { + "about": "the smallest plugin, one callback per function and one at the end", + "argv": [ + "gcc-16", + "-O2", + "-S", + "-fplugin=gxplug/examples/hello.so", + "corpora/programs/l2.c", + "-o", + "b05-scratch.s" + ], + "returncode": 0, + "stdout": "", + "stderr": "hello: dist2\nhello: nearest\nhello, world: 2 function(s) in this unit\n", + "asm": "\t.arch armv8.5-a\n\t.build_version macos, 15, 0\n\t.text\n\t.align\t2\n\t.p2align 5,,15\n\t.globl _nearest\n_nearest:\nLFB1:\n\tcmp\tw1, 1\n\tble\tL5\n\tldp\tw7, w3, [x0]\n\tasr\tx8, x2, 32\n\tadd\tx5, x0, 8\n\tmov\tw6, 1\n\tmov\tw0, 0\n\tsub\tw3, w3, w8\n\tsub\tw7, w7, w2\n\tmul\tw3, w3, w3\n\tmadd\tw7, w7, w7, w3\n\t.p2align 5,,15\nL4:\n\tldp\tw4, w3, [x5], 8\n\tsub\tw4, w4, w2\n\tsub\tw3, w3, w8\n\tmul\tw4, w4, w4\n\tmadd\tw3, w3, w3, w4\n\tcmp\tw7, w3\n\tcsel\tw0, w0, w6, le\n\tadd\tw6, w6, 1\n\tcsel\tw7, w7, w3, le\n\tcmp\tw1, w6\n\tbne\tL4\n\tret\n\t.p2align 2,,3\nL5:\n\tmov\tw0, 0\n\tret\nLFE1:\n\t.section __TEXT,__eh_frame,coalesced,no_toc+strip_static_syms+live_support\nEH_frame1:\n\t.set L$set$0,LECIE1-LSCIE1\n\t.long L$set$0\nLSCIE1:\n\t.long\t0\n\t.byte\t0x3\n\t.ascii \"zR\\0\"\n\t.uleb128 0x1\n\t.sleb128 -8\n\t.uleb128 0x1e\n\t.uleb128 0x1\n\t.byte\t0x10\n\t.byte\t0xc\n\t.uleb128 0x1f\n\t.uleb128 0\n\t.align\t3\nLECIE1:\nLSFDE1:\n\t.set L$set$1,LEFDE1-LASFDE1\n\t.long L$set$1\nLASFDE1:\n\t.long\tLASFDE1-EH_frame1\n\t.quad\tLFB1-.\n\t.set L$set$2,LFE1-LFB1\n\t.quad L$set$2\n\t.uleb128 0\n\t.align\t3\nLEFDE1:\n\t.ident\t\"GCC: (Homebrew GCC 16.2.0) 16.2.0\"\n\t.subsections_via_symbols\n" + }, + "hello-arg": { + "about": "the same plugin, given an argument", + "argv": [ + "gcc-16", + "-O2", + "-S", + "-fplugin=gxplug/examples/hello.so", + "-fplugin-arg-hello-who=reader", + "corpora/programs/l2.c", + "-o", + "b05-scratch.s" + ], + "returncode": 0, + "stdout": "", + "stderr": "hello: dist2\nhello: nearest\nhello, reader: 2 function(s) in this unit\n" + }, + "countpass": { + "about": "a pass of your own, running after ssa on every function", + "argv": [ + "gcc-16", + "-O2", + "-S", + "-fplugin=gxplug/examples/countpass.so", + "corpora/programs/l2.c", + "-o", + "b05-scratch.s" + ], + "returncode": 0, + "stdout": "", + "stderr": "gxcount: dist2 has 3 block(s), 0 phi(s), 10 statement(s)\ngxcount: nearest has 8 block(s), 6 phi(s), 14 statement(s)\n", + "asm": "\t.arch armv8.5-a\n\t.build_version macos, 15, 0\n\t.text\n\t.align\t2\n\t.p2align 5,,15\n\t.globl _nearest\n_nearest:\nLFB1:\n\tcmp\tw1, 1\n\tble\tL5\n\tldp\tw7, w3, [x0]\n\tasr\tx8, x2, 32\n\tadd\tx5, x0, 8\n\tmov\tw6, 1\n\tmov\tw0, 0\n\tsub\tw3, w3, w8\n\tsub\tw7, w7, w2\n\tmul\tw3, w3, w3\n\tmadd\tw7, w7, w7, w3\n\t.p2align 5,,15\nL4:\n\tldp\tw4, w3, [x5], 8\n\tsub\tw4, w4, w2\n\tsub\tw3, w3, w8\n\tmul\tw4, w4, w4\n\tmadd\tw3, w3, w3, w4\n\tcmp\tw7, w3\n\tcsel\tw0, w0, w6, le\n\tadd\tw6, w6, 1\n\tcsel\tw7, w7, w3, le\n\tcmp\tw1, w6\n\tbne\tL4\n\tret\n\t.p2align 2,,3\nL5:\n\tmov\tw0, 0\n\tret\nLFE1:\n\t.section __TEXT,__eh_frame,coalesced,no_toc+strip_static_syms+live_support\nEH_frame1:\n\t.set L$set$0,LECIE1-LSCIE1\n\t.long L$set$0\nLSCIE1:\n\t.long\t0\n\t.byte\t0x3\n\t.ascii \"zR\\0\"\n\t.uleb128 0x1\n\t.sleb128 -8\n\t.uleb128 0x1e\n\t.uleb128 0x1\n\t.byte\t0x10\n\t.byte\t0xc\n\t.uleb128 0x1f\n\t.uleb128 0\n\t.align\t3\nLECIE1:\nLSFDE1:\n\t.set L$set$1,LEFDE1-LASFDE1\n\t.long L$set$1\nLASFDE1:\n\t.long\tLASFDE1-EH_frame1\n\t.quad\tLFB1-.\n\t.set L$set$2,LFE1-LFB1\n\t.quad L$set$2\n\t.uleb128 0\n\t.align\t3\nLEFDE1:\n\t.ident\t\"GCC: (Homebrew GCC 16.2.0) 16.2.0\"\n\t.subsections_via_symbols\n" + }, + "gated": { + "about": "ivopts switched off from outside, and the assembly moves", + "argv": [ + "gcc-16", + "-O2", + "-S", + "-fplugin=gxplug/examples/gate.so", + "-fplugin-arg-gate-off=ivopts", + "corpora/programs/l2.c", + "-o", + "b05-scratch.s" + ], + "returncode": 0, + "stdout": "", + "stderr": "gate: refused ivopts 1 time(s)\n", + "asm": "\t.arch armv8.5-a\n\t.build_version macos, 15, 0\n\t.text\n\t.align\t2\n\t.p2align 5,,15\n\t.globl _nearest\n_nearest:\nLFB1:\n\tcmp\tw1, 1\n\tble\tL5\n\tldp\tw6, w3, [x0]\n\tasr\tx9, x2, 32\n\tmov\tw5, 1\n\tmov\tw7, 0\n\tsub\tw3, w3, w9\n\tsub\tw6, w6, w2\n\tmul\tw3, w3, w3\n\tmadd\tw6, w6, w6, w3\n\t.p2align 5,,15\nL4:\n\tubfiz\tx3, x5, 3, 32\n\tldr\tw4, [x0, x3]\n\tadd\tx8, x0, x3\n\tldr\tw3, [x8, 4]\n\tsub\tw4, w4, w2\n\tmul\tw4, w4, w4\n\tsub\tw3, w3, w9\n\tmadd\tw3, w3, w3, w4\n\tcmp\tw6, w3\n\tcsel\tw7, w7, w5, le\n\tadd\tw5, w5, 1\n\tcsel\tw6, w6, w3, le\n\tcmp\tw1, w5\n\tbgt\tL4\n\tmov\tw0, w7\n\tret\n\t.p2align 2,,3\nL5:\n\tmov\tw7, 0\n\tmov\tw0, w7\n\tret\nLFE1:\n\t.section __TEXT,__eh_frame,coalesced,no_toc+strip_static_syms+live_support\nEH_frame1:\n\t.set L$set$0,LECIE1-LSCIE1\n\t.long L$set$0\nLSCIE1:\n\t.long\t0\n\t.byte\t0x3\n\t.ascii \"zR\\0\"\n\t.uleb128 0x1\n\t.sleb128 -8\n\t.uleb128 0x1e\n\t.uleb128 0x1\n\t.byte\t0x10\n\t.byte\t0xc\n\t.uleb128 0x1f\n\t.uleb128 0\n\t.align\t3\nLECIE1:\nLSFDE1:\n\t.set L$set$1,LEFDE1-LASFDE1\n\t.long L$set$1\nLASFDE1:\n\t.long\tLASFDE1-EH_frame1\n\t.quad\tLFB1-.\n\t.set L$set$2,LFE1-LFB1\n\t.quad L$set$2\n\t.uleb128 0\n\t.align\t3\nLEFDE1:\n\t.ident\t\"GCC: (Homebrew GCC 16.2.0) 16.2.0\"\n\t.subsections_via_symbols\n" + }, + "ungated": { + "about": "einline switched off the same way, and nothing moves", + "argv": [ + "gcc-16", + "-O2", + "-S", + "-fplugin=gxplug/examples/gate.so", + "-fplugin-arg-gate-off=einline", + "corpora/programs/l2.c", + "-o", + "b05-scratch.s" + ], + "returncode": 0, + "stdout": "", + "stderr": "gate: refused einline 2 time(s)\n", + "asm": "\t.arch armv8.5-a\n\t.build_version macos, 15, 0\n\t.text\n\t.align\t2\n\t.p2align 5,,15\n\t.globl _nearest\n_nearest:\nLFB1:\n\tcmp\tw1, 1\n\tble\tL5\n\tldp\tw7, w3, [x0]\n\tasr\tx8, x2, 32\n\tadd\tx5, x0, 8\n\tmov\tw6, 1\n\tmov\tw0, 0\n\tsub\tw3, w3, w8\n\tsub\tw7, w7, w2\n\tmul\tw3, w3, w3\n\tmadd\tw7, w7, w7, w3\n\t.p2align 5,,15\nL4:\n\tldp\tw4, w3, [x5], 8\n\tsub\tw4, w4, w2\n\tsub\tw3, w3, w8\n\tmul\tw4, w4, w4\n\tmadd\tw3, w3, w3, w4\n\tcmp\tw7, w3\n\tcsel\tw0, w0, w6, le\n\tadd\tw6, w6, 1\n\tcsel\tw7, w7, w3, le\n\tcmp\tw1, w6\n\tbne\tL4\n\tret\n\t.p2align 2,,3\nL5:\n\tmov\tw0, 0\n\tret\nLFE1:\n\t.section __TEXT,__eh_frame,coalesced,no_toc+strip_static_syms+live_support\nEH_frame1:\n\t.set L$set$0,LECIE1-LSCIE1\n\t.long L$set$0\nLSCIE1:\n\t.long\t0\n\t.byte\t0x3\n\t.ascii \"zR\\0\"\n\t.uleb128 0x1\n\t.sleb128 -8\n\t.uleb128 0x1e\n\t.uleb128 0x1\n\t.byte\t0x10\n\t.byte\t0xc\n\t.uleb128 0x1f\n\t.uleb128 0\n\t.align\t3\nLECIE1:\nLSFDE1:\n\t.set L$set$1,LEFDE1-LASFDE1\n\t.long L$set$1\nLASFDE1:\n\t.long\tLASFDE1-EH_frame1\n\t.quad\tLFB1-.\n\t.set L$set$2,LFE1-LFB1\n\t.quad L$set$2\n\t.uleb128 0\n\t.align\t3\nLEFDE1:\n\t.ident\t\"GCC: (Homebrew GCC 16.2.0) 16.2.0\"\n\t.subsections_via_symbols\n" + }, + "gate-dumpname": { + "about": "the same request with a dump name instead of a pass name, which matches nothing", + "argv": [ + "gcc-16", + "-O2", + "-S", + "-fplugin=gxplug/examples/gate.so", + "-fplugin-arg-gate-off=cddce1", + "corpora/programs/l2.c", + "-o", + "b05-scratch.s" + ], + "returncode": 0, + "stdout": "", + "stderr": "gate: refused cddce1 0 time(s)\n" + }, + "nolicence": { + "about": "refused before plugin_init runs, for a symbol that is not there", + "argv": [ + "gcc-16", + "-O2", + "-S", + "-fplugin=gxplug/examples/nolicence.so", + "corpora/programs/l2.c", + "-o", + "b05-scratch.s" + ], + "returncode": 1, + "stdout": "", + "stderr": "cc1: fatal error: plugin gxplug/examples/nolicence.so is not licensed under a GPL-compatible license: dlsym(0x72706b10, plugin_is_GPL_compatible): symbol not found\ncompilation terminated.\n" + }, + "wrongver": { + "about": "refused by its own version check, which is the plugin's job and not GCC's", + "argv": [ + "gcc-16", + "-O2", + "-S", + "-fplugin=gxplug/examples/wrongver.so", + "corpora/programs/l2.c", + "-o", + "b05-scratch.s" + ], + "returncode": 1, + "stdout": "", + "stderr": "cc1: error: wrongver: built against GCC 15.1.0, loaded into GCC 16.2.0\ncc1: note: wrongver: really built by GCC 16.2.0, the mismatch is staged\ncc1: error: failed to initialize plugin gxplug/examples/wrongver.so\n" + }, + "missing": { + "about": "a plugin that is not there, which is a dlopen error and not a driver error", + "argv": [ + "gcc-16", + "-O2", + "-S", + "-fplugin=./no-such-plugin.so", + "corpora/programs/l2.c", + "-o", + "b05-scratch.s" + ], + "returncode": 1, + "stdout": "", + "stderr": "cc1: error: cannot load plugin ./no-such-plugin.so: dlopen(./no-such-plugin.so, 0x000A): tried: './no-such-plugin.so' (no such file), '/System/Volumes/Preboot/Cryptexes/OS./no-such-plugin.so' (no such file), '/usr/lib/./no-such-plugin.so' (no such file, not in dyld cache), './no-such-plugin.so' (no such file), 'no-such-plugin.so' (no such file), '/System/Volumes/Preboot/Cryptexes/OSno-such-plugin.so' (no such file), 'no-such-plugin.so' (no such file)\n" + }, + "badarg": { + "about": "a mistyped -fplugin-arg, which gxplug refuses rather than ignoring", + "argv": [ + "gcc-16", + "-O2", + "-S", + "-fplugin=gxplug/gxplug.so", + "-fplugin-arg-gxplug-bogus=1", + "corpora/programs/l2.c", + "-o", + "b05-scratch.s" + ], + "returncode": 1, + "stdout": "", + "stderr": "cc1: error: gxplug: unknown argument 'bogus'\ncc1: error: failed to initialize plugin gxplug/gxplug.so\n" + } + } +} diff --git a/corpora/source/b05.json b/corpora/source/b05.json new file mode 100644 index 0000000..fedfc4d --- /dev/null +++ b/corpora/source/b05.json @@ -0,0 +1,237 @@ +{ + "tag": "releases/gcc-16.2.0", + "snippets": { + "dlopen": { + "path": "gcc/plugin.cc", + "first": 699, + "last": 708, + "about": "How a plugin is loaded, and the one flag that decides what a mismatch does", + "lines": [ + " /* We use RTLD_NOW to accelerate binding and detect any mismatch", + " between the API expected by the plugin and the GCC API; we use", + " RTLD_GLOBAL which is useful to plugins which themselves call", + " dlopen. */", + " dl_handle = dlopen (plugin->full_name, RTLD_NOW | RTLD_GLOBAL);", + " if (!dl_handle)", + " {", + " error (\"cannot load plugin %s: %s\", plugin->full_name, dlerror ());", + " return false;", + " }" + ] + }, + "licence": { + "path": "gcc/plugin.cc", + "first": 713, + "last": 717, + "about": "The licence check: a symbol looked up by name, and a fatal error if absent", + "lines": [ + " /* Check the plugin license. */", + " if (dlsym (dl_handle, str_license) == NULL)", + " fatal_error (input_location,", + "\t\t \"plugin %s is not licensed under a GPL-compatible license:\"", + "\t\t \" %s\", plugin->full_name, dlerror ());" + ] + }, + "initcall": { + "path": "gcc/plugin.cc", + "first": 731, + "last": 740, + "about": "Calling plugin_init, and what a non-zero return does to the compilation", + "lines": [ + " /* Call the plugin-provided initialization routine with the arguments. */", + " if ((*plugin_init) (plugin, &gcc_version))", + " {", + " dlclose(dl_handle);", + " error (\"failed to initialize plugin %s\", plugin->full_name);", + " return false;", + " }", + " /* leak dl_handle on purpose to ensure the plugin is loaded for the", + " entire run of the compiler. */", + " return true;" + ] + }, + "version": { + "path": "gcc/plugin.cc", + "first": 1004, + "last": 1025, + "about": "The default version check, which compares five fields and not one", + "lines": [ + "/* The default version check. Compares every field in VERSION. */", + "", + "bool", + "plugin_default_version_check (struct plugin_gcc_version *gcc_version,", + "\t\t\t struct plugin_gcc_version *plugin_version)", + "{", + " if (!gcc_version || !plugin_version)", + " return false;", + "", + " if (strcmp (gcc_version->basever, plugin_version->basever))", + " return false;", + " if (strcmp (gcc_version->datestamp, plugin_version->datestamp))", + " return false;", + " if (strcmp (gcc_version->devphase, plugin_version->devphase))", + " return false;", + " if (strcmp (gcc_version->revision, plugin_version->revision))", + " return false;", + " if (strcmp (gcc_version->configuration_arguments,", + "\t plugin_version->configuration_arguments))", + " return false;", + " return true;", + "}" + ] + }, + "defevent": { + "path": "gcc/plugin.def", + "first": 20, + "last": 39, + "about": "The event list is a file of one-line macro calls, and the order is the ABI", + "lines": [ + "/* Called before parsing the body of a function. */", + "DEFEVENT (PLUGIN_START_PARSE_FUNCTION)", + "", + "/* After finishing parsing a function. */", + "DEFEVENT (PLUGIN_FINISH_PARSE_FUNCTION)", + "", + "/* To hook into pass manager. */", + "DEFEVENT (PLUGIN_PASS_MANAGER_SETUP)", + "", + "/* After finishing parsing a type. */", + "DEFEVENT (PLUGIN_FINISH_TYPE)", + "", + "/* After finishing parsing a declaration. */", + "DEFEVENT (PLUGIN_FINISH_DECL)", + "", + "/* Useful for summary processing. */", + "DEFEVENT (PLUGIN_FINISH_UNIT)", + "", + "/* Allows to see low level AST in C and C++ frontends. */", + "DEFEVENT (PLUGIN_PRE_GENERICIZE)" + ] + }, + "pseudo": { + "path": "gcc/plugin.cc", + "first": 450, + "last": 469, + "about": "register_callback, where three of the events are handled and never fired", + "lines": [ + "void", + "register_callback (const char *plugin_name,", + "\t\t int event,", + " plugin_callback_func callback,", + " void *user_data)", + "{", + " switch (event)", + " {", + " case PLUGIN_PASS_MANAGER_SETUP:", + "\tgcc_assert (!callback);", + " register_pass ((struct register_pass_info *) user_data);", + " break;", + " case PLUGIN_INFO:", + "\tgcc_assert (!callback);", + "\tregister_plugin_info (plugin_name, (struct plugin_info *) user_data);", + "\tbreak;", + " case PLUGIN_REGISTER_GGC_ROOTS:", + "\tgcc_assert (!callback);", + " ggc_register_root_tab ((const struct ggc_root_tab*) user_data);", + "\tbreak;" + ] + }, + "dispatch": { + "path": "gcc/plugin.cc", + "first": 582, + "last": 592, + "about": "What firing an event actually is: a linked list walked in registration order", + "lines": [ + " {", + " /* Iterate over every callback registered with this event and", + " call it. */", + " struct callback_info *callback = plugin_callbacks[event];", + "", + "\t if (!callback)", + "\t retval = PLUGEVT_NO_CALLBACK;", + " for ( ; callback; callback = callback->next)", + " (*callback->func) (gcc_data, callback->user_data);", + " }", + " break;" + ] + }, + "info": { + "path": "gcc/tree-pass.h", + "first": 328, + "last": 344, + "about": "The four fields that decide where a plugin's pass goes", + "lines": [ + "enum pass_positioning_ops", + "{", + " PASS_POS_INSERT_AFTER, /* Insert after the reference pass. */", + " PASS_POS_INSERT_BEFORE, /* Insert before the reference pass. */", + " PASS_POS_REPLACE /* Replace the reference pass. */", + "};", + "", + "struct register_pass_info", + "{", + " opt_pass *pass;\t\t /* New pass to register. */", + " const char *reference_pass_name; /* Name of the reference pass for hooking", + " up the new pass. */", + " int ref_pass_instance_number; /* Insert the pass at the specified", + " instance number of the reference pass.", + " Do it for every instance if it is 0. */", + " enum pass_positioning_ops pos_op; /* how to insert the new pass. */", + "};" + ] + }, + "position": { + "path": "gcc/passes.cc", + "first": 1377, + "last": 1401, + "about": "The match: same pass type, same name, and the right instance", + "lines": [ + "/* Insert the pass at the proper position. Return true if the pass", + " is successfully added.", + "", + " NEW_PASS_INFO - new pass to be inserted", + " PASS_LIST - root of the pass list to insert the new pass to */", + "", + "static bool", + "position_pass (struct register_pass_info *new_pass_info, opt_pass **pass_list)", + "{", + " opt_pass *pass = *pass_list, *prev_pass = NULL;", + " bool success = false;", + "", + " for ( ; pass; prev_pass = pass, pass = pass->next)", + " {", + " /* Check if the current pass is of the same type as the new pass and", + " matches the name and the instance number of the reference pass. */", + " if (pass->type == new_pass_info->pass->type", + " && pass->name", + " && !strcmp (pass->name, new_pass_info->reference_pass_name)", + " && ((new_pass_info->ref_pass_instance_number == 0)", + " || (new_pass_info->ref_pass_instance_number ==", + " pass->static_pass_number)", + " || (new_pass_info->ref_pass_instance_number == 1", + " && pass->todo_flags_start & TODO_mark_first_instance)))", + " {" + ] + }, + "gatecall": { + "path": "gcc/passes.cc", + "first": 2597, + "last": 2608, + "about": "The gate, and the one event that is handed a pointer to write through", + "lines": [ + " current_pass = pass;", + "", + " /* Check whether gate check should be avoided.", + " User controls the value of the gate through the parameter \"gate_status\". */", + " gate_status = pass->gate (cfun);", + " gate_status = override_gate_status (pass, current_function_decl, gate_status);", + "", + " /* Override gate with plugin. */", + " invoke_plugin_callbacks (PLUGIN_OVERRIDE_GATE, &gate_status);", + "", + " if (!gate_status)", + " {" + ] + } + } +} diff --git a/docs/lessons.md b/docs/lessons.md index 05a4411..14a0f87 100644 --- a/docs/lessons.md +++ b/docs/lessons.md @@ -22,5 +22,6 @@ Every one is a notebook with a Colab badge, so there is nothing to install. Part | B02 | [Building it three times, and the one thing that proves](https://github.com/tamnd/gcc-internals/blob/main/lessons/b02-the-bootstrap/b02.ipynb) | Why a compiler is built three times and compared twice, what that comparison can and cannot catch, the six files it is told to ignore, and a real stage comparison failure induced on purpose so you can see what it looks like before it happens to you | M2 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/gcc-internals/blob/main/lessons/b02-the-bootstrap/b02.ipynb) | | B03 | [A debugger on the compiler, and the counter for when you cannot have one](https://github.com/tamnd/gcc-internals/blob/main/lessons/b03-the-debugger/b03.ipynb) | A real gdb session against a real cc1, recorded command by command: what the compiler's own gdbinit did before you typed anything, how to stop on one pass out of several hundred, how to read a function from a breakpoint, and how to find the exact transformation that broke your code by bisecting a debug counter instead | M2 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/gcc-internals/blob/main/lessons/b03-the-debugger/b03.ipynb) | | B04 | [Forty thousand tests, and how to run one of them](https://github.com/tamnd/gcc-internals/blob/main/lessons/b04-the-test-suite/b04.ipynb) | The GCC test suite taken apart: what a directive is, why the command line is not what the file says, the subtractive rule that makes a test fail for output nobody asked about, the six compilations every torture test costs, how to run one file without running the other thirty five thousand, and the one question worth asking of a .sum | M2 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/gcc-internals/blob/main/lessons/b04-the-test-suite/b04.ipynb) | +| B05 | [Sixty lines of C++, and you are inside the compiler](https://github.com/tamnd/gcc-internals/blob/main/lessons/b05-the-plugin/b05.ipynb) | GCC's plugin mechanism from the outside in: the three things a plugin has to have, the three ways it is refused, what an event actually is, a GIMPLE pass of your own inserted after ssa with its own dump file, switching one of GCC's passes off from outside and watching the assembly move, and why the thing you are writing against is not an API | M2 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/gcc-internals/blob/main/lessons/b05-the-plugin/b05.ipynb) | -16 of 96 written. +17 of 96 written. diff --git a/gxplug/Makefile b/gxplug/Makefile index 84f87fb..f78d094 100644 --- a/gxplug/Makefile +++ b/gxplug/Makefile @@ -61,7 +61,13 @@ endif PLUGIN := gxplug.so SRC := src/gxplug.cc -.PHONY: all check probe clean +# The five plugins B05 is built on. Two work, one changes the generated code on purpose, +# and two are refused at load time on purpose. They are built with exactly the flags above, +# because a reader's own plugin will be too. +EXAMPLE_SRC := $(wildcard examples/*.cc) +EXAMPLES := $(EXAMPLE_SRC:.cc=.so) + +.PHONY: all check probe clean examples examples-check all: $(PLUGIN) @@ -117,5 +123,47 @@ check: $(PLUGIN) test "$$events" -gt 0 || { echo "gxplug: FAIL, no events emitted"; exit 1; }; \ echo "gxplug: assembly identical with and without the plugin, $$events events" +# The examples, built the same way. No header probe here: if the main plugin built, these +# will, and if it did not the reader has already been told which package to install. +examples: $(EXAMPLES) + +examples/%.so: examples/%.cc $(PLUGIN) + $(CXX_FOR_PLUGIN) $(GXPLUG_CPPFLAGS) $(CPPFLAGS) $(CXXFLAGS) -shared $(LDFLAGS_PLUGIN) -o $@ $< + +# Each example does what B05 says it does. hello and countpass leave the assembly alone, +# gate changes it, and the two broken ones are refused before they run a line. +# +# The program is an array traversal rather than the accumulator `check` uses, because the +# pass being switched off is ivopts and a loop with no memory reference in it gives ivopts +# nothing to rewrite. The two halves of the gate result are checked separately: how many +# times the pass was refused, which catches a pass name that matched nothing, and whether +# the assembly moved, which catches a pass whose absence makes no difference. They fail for +# different reasons and the messages have to say which. +examples-check: examples + @set -e; \ + tmp=$$(mktemp -d); \ + trap 'rm -rf "$$tmp"' EXIT; \ + printf 'int f (int *a, int n) { int s = 0; for (int i = 0; i < n; i++) s += a[i]; return s; }\n' > "$$tmp/t.c"; \ + $(GCC) -O2 -S "$$tmp/t.c" -o "$$tmp/plain.s"; \ + for one in hello countpass; do \ + $(GCC) -O2 -S -fplugin=./examples/$$one.so "$$tmp/t.c" -o "$$tmp/$$one.s" 2>/dev/null; \ + cmp -s "$$tmp/plain.s" "$$tmp/$$one.s" \ + || { echo "examples: FAIL, $$one changed the generated code"; exit 1; }; \ + echo "examples: $$one loaded and changed nothing"; \ + done; \ + $(GCC) -O2 -S -fplugin=./examples/gate.so -fplugin-arg-gate-off=ivopts \ + "$$tmp/t.c" -o "$$tmp/gate.s" 2>"$$tmp/gate.err"; \ + grep -q 'refused ivopts 0 time' "$$tmp/gate.err" \ + && { echo "examples: FAIL, no pass called ivopts was ever asked, so nothing was tested"; \ + cat "$$tmp/gate.err"; exit 1; }; \ + cmp -s "$$tmp/plain.s" "$$tmp/gate.s" \ + && { echo "examples: FAIL, ivopts was switched off and the assembly did not move"; exit 1; }; \ + echo "examples: gate switched a pass off and the assembly moved"; \ + for one in nolicence wrongver; do \ + ! $(GCC) -O2 -S -fplugin=./examples/$$one.so "$$tmp/t.c" -o "$$tmp/$$one.s" 2>/dev/null \ + || { echo "examples: FAIL, $$one was supposed to be refused"; exit 1; }; \ + echo "examples: $$one was refused, as it should be"; \ + done + clean: - rm -f $(PLUGIN) + rm -f $(PLUGIN) $(EXAMPLES) diff --git a/gxplug/README.md b/gxplug/README.md index 8f21e3b..093c78d 100644 --- a/gxplug/README.md +++ b/gxplug/README.md @@ -51,6 +51,27 @@ With no arguments the stream goes to stderr, so that adding the flag and nothing The second is how a notebook reads the stream without a temporary file. Giving both, or giving an argument that is not one of these, is an error and stops the compilation. A typo in a `-fplugin-arg-` name is otherwise the sort of thing that quietly does nothing for an hour. +## The examples + +`examples/` holds the five plugins B05 is written around. They are not part of `gxplug` and nothing in `gxray` imports them. They exist because a lesson about writing a plugin has to show plugins, and because the three interesting things a plugin can do are all things `gxplug` deliberately does not. + +| Plugin | What it is for | +|---|---| +| `hello.cc` | the contract, in the smallest form that still works | +| `countpass.cc` | a GIMPLE pass of your own, inserted after `ssa`, with its own dump file | +| `gate.cc` | switching one of GCC's own passes off, which does change the generated code | +| `nolicence.cc` | `hello.cc` with the GPL symbol deleted, so it is refused at load time | +| `wrongver.cc` | a version check against a GCC that is not this one, so it refuses itself | + +```sh +make examples GCC=gcc-16 # build all five +make examples-check GCC=gcc-16 # build them, load them, and check each does what it claims +``` + +`examples-check` is the part that matters. It asserts that `hello` and `countpass` leave the assembly byte identical, that `gate` does not, and that the two broken ones fail the compilation rather than being skipped. The `plugin` workflow runs it on all four channels, which is what stops the lesson from describing plugins that work only where they were written. + +`gate.cc` takes the name of a pass, and the name it wants is the pass name and not the dump name. `-fdump-tree-cddce1` is the pass called `cddce` on its first instance, so `off=cddce1` matches nothing and reports nothing, which is why the plugin prints how many times it refused something. + ## What a record looks like Two records per pass. A `pass-start` when the pass manager is about to run it, and a `pass-end` carrying the duration and the state the pass left behind. diff --git a/gxplug/examples/countpass.cc b/gxplug/examples/countpass.cc new file mode 100644 index 0000000..21a0ac8 --- /dev/null +++ b/gxplug/examples/countpass.cc @@ -0,0 +1,102 @@ +/* countpass — a pass of your own, inserted between two of GCC's. + + The interesting half of the plugin mechanism. A callback watches; a registered pass is + *in* the pipeline, gets its own entry in -fdump-passes, its own dump file, and runs at a + point you choose relative to a pass that already exists. + + The choice is three fields of one struct. reference_pass_name names an existing pass, + ref_pass_instance_number says which run of it (1 for the first, 0 for every one), and + pos_op says before, after, or instead of. Here: after the first run of the pass named + "ssa", which is where the function has just acquired SSA form. + + This pass reads and prints. It does not modify the IR, so the assembly comes out + unchanged, which examples/check in the Makefile insists on. + + gcc-16 -O2 -S -fplugin=./examples/countpass.so t.c -o t.s + + Copyright the gcc-internals authors. + SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "gcc-plugin.h" +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "tree.h" +#include "tree-pass.h" +#include "context.h" +#include "function.h" +#include "basic-block.h" +#include "gimple.h" +#include "gimple-iterator.h" +#include "ssa.h" +#include "plugin-version.h" + +int plugin_is_GPL_compatible; + +namespace { + +/* Everything a pass has to say about itself before the pass manager will take it. The + fields are positional and the order is fixed by the struct in tree-pass.h; getting one + wrong is a compile error here rather than something that goes wrong at run time. */ +const pass_data count_pass_data = { + GIMPLE_PASS, /* type: what IR this pass wants */ + "gxcount", /* name: appears in -fdump-passes and names the dump file */ + OPTGROUP_NONE, /* optinfo_flags */ + TV_NONE, /* tv_id: no timer of its own */ + PROP_ssa, /* properties_required: refuse to run before SSA exists */ + 0, /* properties_provided */ + 0, /* properties_destroyed */ + 0, /* todo_flags_start */ + 0, /* todo_flags_finish: nothing to clean up, because nothing changed */ +}; + +class count_pass : public gimple_opt_pass +{ +public: + count_pass (gcc::context *ctxt) : gimple_opt_pass (count_pass_data, ctxt) {} + + /* Every pass has a gate and a body. A gate returning false is why -fdump-passes prints + OFF next to a pass on a given function. */ + bool gate (function *) final override { return true; } + + unsigned int execute (function *fun) final override + { + int statements = 0, phis = 0; + basic_block bb; + FOR_EACH_BB_FN (bb, fun) + { + for (gphi_iterator gsi = gsi_start_phis (bb); !gsi_end_p (gsi); gsi_next (&gsi)) + phis++; + for (gimple_stmt_iterator gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi)) + statements++; + } + fprintf (stderr, "gxcount: %s has %d block(s), %d phi(s), %d statement(s)\n", + function_name (fun), n_basic_blocks_for_fn (fun), phis, statements); + + /* The return value is a TODO mask: what the pass manager should do now that this pass + has run. Zero means nothing changed and nothing needs cleaning up. A pass that + modified the IR and returned zero is how a plugin corrupts a compilation. */ + return 0; + } +}; + +} /* anonymous namespace */ + +int +plugin_init (plugin_name_args *info, plugin_gcc_version *version) +{ + if (!plugin_default_version_check (version, &gcc_version)) + return 1; + + /* PLUGIN_PASS_MANAGER_SETUP is not an event. It is never fired; register_callback sees + the name and does the insertion there and then, using the fourth argument, which for + every real event would be user data handed back to a callback later. */ + struct register_pass_info info_for_pass; + info_for_pass.pass = new count_pass (g); + info_for_pass.reference_pass_name = "ssa"; + info_for_pass.ref_pass_instance_number = 1; + info_for_pass.pos_op = PASS_POS_INSERT_AFTER; + + register_callback (info->base_name, PLUGIN_PASS_MANAGER_SETUP, nullptr, &info_for_pass); + return 0; +} diff --git a/gxplug/examples/gate.cc b/gxplug/examples/gate.cc new file mode 100644 index 0000000..833d832 --- /dev/null +++ b/gxplug/examples/gate.cc @@ -0,0 +1,88 @@ +/* gate — switching one of GCC's own passes off from outside. + + PLUGIN_OVERRIDE_GATE is the only event whose data is written rather than read. The pass + manager has just asked a pass whether it wants to run, and it hands the plugin the + address of the answer. Assigning through that pointer changes the decision. + + There is no command line flag for most of the passes this can reach, and no patch + involved. It is also the point at which a plugin stops being an observer: the assembly + that comes out is different, and it is different because of you. + + gcc-16 -O2 -S -fplugin=./examples/gate.so -fplugin-arg-gate-off=ivopts t.c -o t.s + + The name to give is the pass name, not the dump name. -fdump-tree-cddce1 is the pass + called "cddce" on its first instance, and asking to switch off "cddce1" matches nothing + and reports nothing, which is why this prints a count. + + Copyright the gcc-internals authors. + SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "gcc-plugin.h" +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "diagnostic-core.h" +#include "tree.h" +#include "tree-pass.h" +#include "context.h" +#include "function.h" +#include "plugin-version.h" + +int plugin_is_GPL_compatible; + +namespace { + +const char *turn_off = nullptr; +int refusals = 0; + +void +on_gate (void *gcc_data, void * /*user_data*/) +{ + bool *gate_status = (bool *) gcc_data; + + /* Which pass is being asked is not in the data. It is in current_pass, a global the pass + manager sets before it asks, which is the sort of thing that is obvious once you have + read passes.cc and undiscoverable before. */ + if (!current_pass || !current_pass->name || !turn_off) + return; + if (strcmp (current_pass->name, turn_off) != 0) + return; + + /* Only ever turning a pass off. Turning one *on* by writing true here runs a pass whose + own gate said no, which usually means its preconditions are not met. */ + if (*gate_status) + { + *gate_status = false; + refusals++; + } +} + +void +on_finish (void * /*gcc_data*/, void * /*user_data*/) +{ + fprintf (stderr, "gate: refused %s %d time(s)\n", turn_off ? turn_off : "(nothing)", + refusals); +} + +} /* anonymous namespace */ + +int +plugin_init (plugin_name_args *info, plugin_gcc_version *version) +{ + if (!plugin_default_version_check (version, &gcc_version)) + return 1; + + for (int i = 0; i < info->argc; i++) + if (strcmp (info->argv[i].key, "off") == 0) + turn_off = info->argv[i].value; + + if (!turn_off) + { + error ("gate: give %qs, the name of a pass to switch off", "off="); + return 1; + } + + register_callback (info->base_name, PLUGIN_OVERRIDE_GATE, on_gate, nullptr); + register_callback (info->base_name, PLUGIN_FINISH, on_finish, nullptr); + return 0; +} diff --git a/gxplug/examples/hello.cc b/gxplug/examples/hello.cc new file mode 100644 index 0000000..b78daad --- /dev/null +++ b/gxplug/examples/hello.cc @@ -0,0 +1,62 @@ +/* hello — the smallest plugin that is still a plugin. + + Forty lines, three of which are the whole of the plugin contract: a symbol that says the + licence is compatible, an entry point called plugin_init, and a version check that GCC + does not perform for you. + + Everything else here is one callback. PLUGIN_PRE_GENERICIZE fires once per function + definition, just after the front end has finished with it and before it is handed to the + middle end, so counting them counts the functions in the translation unit. PLUGIN_FINISH + fires once, at the very end, which is where the total gets printed. + + gcc-16 -O2 -S -fplugin=./examples/hello.so t.c -o t.s + gcc-16 -O2 -S -fplugin=./examples/hello.so -fplugin-arg-hello-who=reader t.c -o t.s + + Copyright the gcc-internals authors. + SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "gcc-plugin.h" +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "tree.h" +#include "plugin-version.h" + +/* GCC checks for this symbol by name before it calls anything, and a plugin without it is + refused with a diagnostic about the licence. It is never read, only found. */ +int plugin_is_GPL_compatible; + +static int functions = 0; +static const char *who = "world"; + +static void +on_function (void *gcc_data, void * /*user_data*/) +{ + tree fndecl = (tree) gcc_data; + functions++; + fprintf (stderr, "hello: %s\n", IDENTIFIER_POINTER (DECL_NAME (fndecl))); +} + +static void +on_finish (void * /*gcc_data*/, void * /*user_data*/) +{ + fprintf (stderr, "hello, %s: %d function(s) in this unit\n", who, functions); +} + +int +plugin_init (plugin_name_args *info, plugin_gcc_version *version) +{ + /* Not a formality. The plugin is compiled against GCC's private headers, so a plugin + built for one compiler and loaded into another is undefined behaviour. Returning + non-zero here stops the compilation with a diagnostic instead. */ + if (!plugin_default_version_check (version, &gcc_version)) + return 1; + + for (int i = 0; i < info->argc; i++) + if (strcmp (info->argv[i].key, "who") == 0) + who = info->argv[i].value; + + register_callback (info->base_name, PLUGIN_PRE_GENERICIZE, on_function, nullptr); + register_callback (info->base_name, PLUGIN_FINISH, on_finish, nullptr); + return 0; +} diff --git a/gxplug/examples/nolicence.cc b/gxplug/examples/nolicence.cc new file mode 100644 index 0000000..f374085 --- /dev/null +++ b/gxplug/examples/nolicence.cc @@ -0,0 +1,31 @@ +/* nolicence — a plugin that is refused, on purpose. + + Identical to hello.cc with one symbol deleted. GCC looks for plugin_is_GPL_compatible by + name after dlopen and before it calls plugin_init, and a plugin without it never runs a + line of its own code. + + This file exists so that the diagnostic is something a reader has seen once, in a lesson, + rather than the first thing they meet on their own plugin at eleven at night. + + Copyright the gcc-internals authors. + SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "gcc-plugin.h" +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "tree.h" +#include "plugin-version.h" + +/* The line that is missing: + + int plugin_is_GPL_compatible; */ + +int +plugin_init (plugin_name_args * /*info*/, plugin_gcc_version *version) +{ + if (!plugin_default_version_check (version, &gcc_version)) + return 1; + fprintf (stderr, "nolicence: this line never prints\n"); + return 0; +} diff --git a/gxplug/examples/wrongver.cc b/gxplug/examples/wrongver.cc new file mode 100644 index 0000000..1fa1b22 --- /dev/null +++ b/gxplug/examples/wrongver.cc @@ -0,0 +1,46 @@ +/* wrongver — what a version mismatch looks like, without needing two compilers. + + plugin_default_version_check compares the plugin_gcc_version GCC passes in against the + one baked into the plugin from plugin-version.h at build time. Normally the second one + is &gcc_version and the check passes because the plugin was built ten seconds ago by the + compiler now loading it. + + Here the second one is a fabricated struct, so the check fails the way it would fail if + this plugin had been built by GCC 15 and dropped into GCC 16. The diagnostic and the exit + are real; only the mismatch is staged. + + Copyright the gcc-internals authors. + SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "gcc-plugin.h" +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "diagnostic-core.h" +#include "tree.h" +#include "plugin-version.h" + +int plugin_is_GPL_compatible; + +/* What the plugin claims it was built by. Every field of this is compared, not only the + version: two compilers of the same version configured differently have incompatible + plugin ABIs, which is why configuration_arguments is in the struct at all. */ +static struct plugin_gcc_version built_by + = { "15.1.0", "20250501", "release", "", "--enable-languages=c,c++" }; + +int +plugin_init (plugin_name_args * /*info*/, plugin_gcc_version *version) +{ + if (!plugin_default_version_check (version, &built_by)) + { + error ("wrongver: built against GCC %s, loaded into GCC %s", built_by.basever, + version->basever); + /* Which is a lie, and here is the truth, so that nobody reading this output in a + lesson concludes their compiler is broken. */ + inform (UNKNOWN_LOCATION, "wrongver: really built by GCC %s, the mismatch is staged", + gcc_version.basever); + return 1; + } + fprintf (stderr, "wrongver: this line never prints\n"); + return 0; +} diff --git a/gxray/glossary.py b/gxray/glossary.py index 9c7e58b..86806b2 100644 --- a/gxray/glossary.py +++ b/gxray/glossary.py @@ -881,6 +881,68 @@ def anchor(name: str) -> str: ) +EXTENDING = Group( + "Extending the compiler", + "Words you need to load code of your own into GCC, put a pass where you want it, and understand why the thing you are writing against is not an API. B05 is the lesson.", + ( + Term( + name="plugin", + short="A shared object loaded into `cc1` at startup, which registers functions to be called at named points during a compilation.", + long="`-fplugin=./thing.so` and the compiler proper `dlopen`s it with `RTLD_NOW`, looks for a symbol called `plugin_is_GPL_compatible` by name, and calls `plugin_init`. Everything a plugin can do follows from being inside the same process: it sees the real IR, calls the real functions, and crashes the real compiler. There are three ways it can be refused, all of them before it runs a line of its own code, and all three stop the compilation rather than warning and carrying on. It is the only supported way to observe or change what GCC does without patching GCC.", + cite="gcc/plugin.cc:699@releases/gcc-16.2.0", + also=("`-fplugin=`", "`plugin_init`", "`-fplugin-arg-`"), + see=("plugin event", "plugin ABI"), + met="B05", + ), + Term( + name="plugin event", + short="A named point in a compilation where GCC calls whatever a plugin registered for it.", + long="`gcc/plugin.def` is a file of twenty six one-line macro calls, and the order of that file is the ABI, because the enumerator's value is the index into the callback table. Twenty three of them are fired, from twenty nine places, and what arrives with each one is a `void *` whose real type is written in the call site and nowhere else. Firing an event is a walk down a linked list of callbacks in registration order, with no return value read, so a plugin cannot refuse an event or stop another plugin from seeing it.", + cite="gcc/plugin.def:20@releases/gcc-16.2.0", + also=("`DEFEVENT`", "`register_callback`", "`invoke_plugin_callbacks`"), + see=("plugin", "pseudo-event"), + met="B05", + ), + Term( + name="pseudo-event", + short="One of the three names in the event list that is never fired, and is instead acted on at the moment you register for it.", + long="`PLUGIN_PASS_MANAGER_SETUP`, `PLUGIN_INFO` and `PLUGIN_REGISTER_GGC_ROOTS` are handled inside `register_callback` itself: it asserts the callback is null and uses the user data argument straight away. So registering a pass is spelled the same way as registering a callback and does something else entirely, which is the single most confusing thing about the mechanism, and the reason a plugin that passes a function pointer alongside a `register_pass_info` gets an assertion failure rather than a diagnostic.", + cite="gcc/plugin.cc:458@releases/gcc-16.2.0", + also=("`PLUGIN_PASS_MANAGER_SETUP`", "`register_callback`"), + see=("plugin event", "pass positioning"), + met="B05", + ), + Term( + name="pass positioning", + short="The four fields that say where in the pipeline a plugin's pass goes: which pass to hang it on, which run of that pass, and before, after or instead of.", + long="`struct register_pass_info` holds the new pass, a `reference_pass_name`, a `ref_pass_instance_number` where zero means every instance and one means the first, and a `pos_op` of `PASS_POS_INSERT_AFTER`, `PASS_POS_INSERT_BEFORE` or `PASS_POS_REPLACE`. The name to give is the pass name and not the dump name: `-fdump-tree-cddce1` is the pass called `cddce` on its first instance, and a reference to `cddce1` matches nothing. A reference that matches nothing is a fatal error at registration, which is the one mistake in this area that tells you about itself.", + cite="gcc/tree-pass.h:328@releases/gcc-16.2.0", + also=("`register_pass_info`", "`PASS_POS_INSERT_AFTER`", "`position_pass`"), + see=("pass", "pseudo-event"), + met="B05", + ), + Term( + name="plugin ABI", + short="The unwritten contract between a plugin and the compiler that loads it, which is every private header GCC has and every option it was configured with.", + long="`plugin_default_version_check` compares five fields, not one: the base version, the datestamp, the development phase, the revision and the whole configuration argument string. Two compilers of the same version built with different `--enable` flags have incompatible plugin ABIs, because those flags change struct layouts. That is why a plugin has to be built by the compiler that will load it, why the check is the plugin's own job rather than the compiler's, and why there is no such thing as shipping a binary plugin.", + cite="gcc/plugin.cc:1013@releases/gcc-16.2.0", + also=("`plugin_default_version_check`", "`plugin-version.h`", "`gcc_version`"), + see=("plugin", "build config"), + met="B05", + ), + Term( + name="TODO flags", + short="What a pass tells the pass manager to do after it has run, returned as a bit mask from `execute`.", + long="`TODO_cleanup_cfg`, `TODO_update_ssa`, `TODO_verify_all` and their neighbours. A pass that changed nothing returns zero. A pass that modified the IR and returned zero has left the compiler believing things that are no longer true, and the failure shows up several passes later in code that did nothing wrong, which is the most expensive mistake a first plugin can make. There is a second set, `todo_flags_start`, that runs before the pass instead, and `TODO_mark_first_instance` in it is how the pass manager knows which run of a repeated pass is the first.", + cite="gcc/tree-pass.h:238@releases/gcc-16.2.0", + also=("`todo_flags_finish`", "`TODO_update_ssa`", "`pass_data`"), + see=("pass", "pass positioning"), + met="B05", + ), + ), +) + + GROUPS: tuple[Group, ...] = ( READING, FINDING, @@ -892,6 +954,7 @@ def anchor(name: str) -> str: TEXT, BUILDING, TESTING, + EXTENDING, ) #: Every term, flattened. diff --git a/gxray/plug.py b/gxray/plug.py index d1595a9..6aac3a5 100644 --- a/gxray/plug.py +++ b/gxray/plug.py @@ -250,3 +250,149 @@ def parse(text: str) -> Stream: def load(path: str | Path) -> Stream: return parse(Path(path).read_text(encoding="utf-8")) + + +# --------------------------------------------------------------------------- +# The recorded half. +# +# Everything above works on a stream a reader produced. Nothing below needs a compiler at +# all: it reads what lessons/b05-the-plugin/record.py wrote after building five plugins +# against a real GCC 16.2 and loading each one. A reader on Colab has the plugin sources, +# because they are in this repository, and no way to build them, because a plugin needs +# GCC's private headers and a matching compiler. This is how they see the output anyway. + +CORPUS = Path(__file__).resolve().parent.parent / "corpora" / "plug" + +#: The five plugins B05 is built on. Read out of the working tree rather than copied into a +#: corpus, so what the notebook shows is what the `plugin` workflow builds on four compilers. +EXAMPLES = Path(__file__).resolve().parent.parent / "gxplug" / "examples" + + +class PlugError(Exception): + pass + + +def example(name: str) -> str: + """The source of one of the example plugins.""" + path = EXAMPLES / f"{name}.cc" + if not path.is_file(): + have = ", ".join(sorted(p.stem for p in EXAMPLES.glob("*.cc"))) + raise PlugError(f"no example called {name!r}. There is: {have}") + return path.read_text(encoding="utf-8") + + +def body(name: str) -> str: + """The same source with its opening comment removed. + + Every example starts with fifteen lines of why, which is right in a file somebody opens + on their own and wrong in a notebook that has just spent a paragraph saying the same + thing. The code starts at the first `#include`. + """ + text = example(name) + where = text.find("#include") + return text[where:] if where >= 0 else text + + +@dataclass(frozen=True) +class Invocation: + """One recorded compilation: what was run, what came back, and what came out.""" + + name: str + about: str + argv: tuple[str, ...] + returncode: int + stdout: str + stderr: str + asm: str | None = None + + @property + def command(self) -> str: + return " ".join(self.argv) + + @property + def refused(self) -> bool: + """Did the compilation fail? + + For four of the recordings this is the point. A plugin that is refused takes the + compilation down with it rather than being skipped, which is the right choice and + surprises people who expect a warning. + """ + return self.returncode != 0 + + @property + def said(self) -> list[str]: + """The lines the plugin and the compiler printed, in order, stdout after stderr. + + stderr first because that is where a plugin writes and where diagnostics go, and + because stdout is usually empty: the compilations are all `-S -o` something. + """ + return [line for line in (self.stderr + self.stdout).splitlines() if line.strip()] + + +@dataclass(frozen=True) +class Session: + """Everything one recording run produced.""" + + recorded: str + compiler: str + target: str + probe: str + invocations: dict[str, Invocation] + stream_text: str + pipeline: dict + + def __getitem__(self, name: str) -> Invocation: + try: + return self.invocations[name] + except KeyError: + known = ", ".join(sorted(self.invocations)) + raise PlugError(f"no recording called {name!r}. There is: {known}") from None + + @property + def stream(self) -> Stream: + """The gxplug event stream, parsed. The same object a live run would give you.""" + return parse(self.stream_text) + + def diff(self, before: str, after: str) -> list[str]: + """Unified diff of the assembly two recordings produced. + + The one number the gate example rests on. Two recordings of the same program by + the same compiler, one of them with a pass switched off from outside. + """ + import difflib + + one, two = self[before], self[after] + if one.asm is None or two.asm is None: + raise PlugError(f"{before} or {after} did not keep its assembly") + return list( + difflib.unified_diff( + one.asm.splitlines(), two.asm.splitlines(), before, after, lineterm="" + ) + ) + + +def load_session(name: str = "b05", root: Path | str | None = None) -> Session: + target = Path(root or CORPUS) / f"{name}.json" + if not target.is_file(): + raise PlugError(f"{target} is not there. Run lessons/b05-the-plugin/record.py.") + raw = json.loads(target.read_text(encoding="utf-8")) + return Session( + recorded=raw["recorded"], + compiler=raw["compiler"], + target=raw["target"], + probe=raw["probe"], + stream_text=raw["stream"], + pipeline=raw["pipeline"], + invocations={ + key: Invocation( + name=key, + about=one["about"], + argv=tuple(one["argv"]), + returncode=one["returncode"], + stdout=one["stdout"], + stderr=one["stderr"], + asm=one.get("asm"), + ) + for key, one in raw["runs"].items() + }, + ) diff --git a/justfile b/justfile index 9a96f95..9bcfd0a 100644 --- a/justfile +++ b/justfile @@ -242,6 +242,21 @@ corpus-b03 build docker="": corpus-b04 *args: {{py}} lessons/b04-the-test-suite/record.py {{args}} +# B05 builds gxplug and the five example plugins against a real GCC 16, loads each one, and +# keeps every byte of every compilation in corpora/plug/b05.json: eleven runs, four of them +# refusals on purpose, the assembly of the ones that produced any, the -fdump-passes listing +# with the plugin's own pass in it, and one gxplug event stream. It also cuts ten spans of +# plugin.cc, plugin.def, tree-pass.h and passes.cc into corpora/source/b05.json. +# +# Needs a gcc-16 with its plugin headers, which is the one thing Compiler Explorer cannot +# stand in for: a plugin is built against the compiler's private headers by the compiler that +# will load it. Set GXPLUG_GCC to use something other than gcc-16. +# +# `just corpus-b05 --check` re-asserts every fact the lesson states against what is already +# committed, without compiling anything, which is what the test suite calls. +corpus-b05 *args: + {{py}} lessons/b05-the-plugin/record.py {{args}} + # Every Tier 0 experiment, run out of the corpus and then out of the cached Compiler Explorer # responses, and checked against each other. No network either way. See tools/tier0 for what # the three kinds of experiment are and why a recorded one is compared byte for byte while a diff --git a/lessons/CLAIMS.md b/lessons/CLAIMS.md index 0e0944b..e4c87b7 100644 --- a/lessons/CLAIMS.md +++ b/lessons/CLAIMS.md @@ -8,7 +8,7 @@ A few true things cannot be shown from a notebook at all: what a pass does to me Claims about GCC's source rather than its behaviour do not live here. Those carry a `path:line@tag` citation and are checked by `refcheck` against the pinned tree. -125 claims across 16 lessons, 4 of them not observable from a notebook. +132 claims across 17 lessons, 4 of them not observable from a notebook. ## C++ for people who will only ever read it @@ -214,3 +214,15 @@ Claims about GCC's source rather than its behaviour do not live here. Those carr | a RUNTESTFLAGS filter matches the base name of a test and not its path, so a glob of pr*.c selects files in every directory the named .exp walks | [`b04-44`](b04-the-test-suite/b04.ipynb) | | when every process walks the same list every test runs exactly once, and when one process walks a list that is one file shorter, one test is never run and another runs twice, and nothing reports either | [`b04-48`](b04-the-test-suite/b04.ipynb) | | two runs whose summary blocks are identical can differ in four separate tests, and the one that matters most is a test that stopped being run, which no count can show you because the total was made up again by a different test | [`b04-54`](b04-the-test-suite/b04.ipynb) | + +## Sixty lines of C++, and you are inside the compiler + +| Claim | Proved by | +| --- | --- | +| a plugin that only observes leaves the generated code byte for byte identical, which is checkable rather than assertable and is checked here for two of the five | [`b05-12`](b05-the-plugin/b05.ipynb) | +| the default version check compares five fields including the full configure argument string, so two builds of the same GCC release configured differently have incompatible plugin ABIs and refuse each other's plugins | [`b05-20`](b05-the-plugin/b05.ipynb) | +| registering a pass is spelled exactly like registering a callback, and is not one: PLUGIN_PASS_MANAGER_SETUP is handled inside register_callback and never fired, so the fourth argument is read immediately rather than handed back to you later | [`b05-32`](b05-the-plugin/b05.ipynb) | +| a pass registered by a plugin is not a special case anywhere downstream: it appears in -fdump-passes between the two passes it was positioned against, and it gets its own numbered dump file under its own name, like every other pass | [`b05-42`](b05-the-plugin/b05.ipynb) | +| a plugin can switch off one of GCC's own optimization passes for one function at a time, with no patch and no command line flag, and the generated code changes because of it | [`b05-48`](b05-the-plugin/b05.ipynb) | +| the name in -fdump-tree-cddce1 is not a pass name: it is the pass called cddce plus the instance number the pass manager gave it, so asking for cddce1 by name matches nothing at all and reports nothing | [`b05-52`](b05-the-plugin/b05.ipynb) | +| most passes that run leave nothing measurable behind: fewer than one in four of the runs on a nine line program change the statement count, the insn count, the block count or the property bits | [`b05-56`](b05-the-plugin/b05.ipynb) | diff --git a/lessons/b05-the-plugin/b05.ipynb b/lessons/b05-the-plugin/b05.ipynb new file mode 100644 index 0000000..d033112 --- /dev/null +++ b/lessons/b05-the-plugin/b05.ipynb @@ -0,0 +1,986 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "b05-01", + "metadata": {}, + "source": [ + "# B05. Sixty lines of C++, and you are inside the compiler\n", + "\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/gcc-internals/blob/main/lessons/b05-the-plugin/b05.ipynb)\n", + "\n", + "Four lessons of this milestone have been about getting a compiler and finding out what it\n", + "did. This one is about putting your own code inside it while it runs.\n", + "\n", + "A [plugin](https://github.com/tamnd/gcc-internals/blob/main/GLOSSARY.md#plugin) is a shared object that `cc1` loads at startup. Once it is in, it is in:\n", + "it holds the real trees, calls the real functions, sees the real IR between two real passes,\n", + "and segfaults the real compiler. There is no sandbox and no translation layer. That is both\n", + "the reason plugins are the right tool for a question the dump files cannot answer, and the\n", + "reason writing one feels like nothing else in this course.\n", + "\n", + "**You cannot build one here.** A plugin is C++ compiled against GCC's private headers, and it\n", + "has to be built by the compiler that will load it. Colab has neither the headers nor a\n", + "matching GCC. So this lesson works the way B03 did: the five plugins are in this repository\n", + "under `gxplug/examples/`, where every line of them can be read, and everything they printed\n", + "was recorded by a real GCC 16.2 and committed.\n", + "\n", + "If you have a compiler from B01 or a container, everything below is four commands away, and\n", + "the section before the boss fight says which four.\n", + "\n", + "**What you come away with**\n", + "\n", + "- The three things a plugin must have, and what happens when each one is missing\n", + "- Why the version check compares the configure line and not just the version\n", + "- What an event actually is, and the three names in the event list that are not events\n", + "- A pass of your own, running after `ssa`, in `-fdump-passes`, with its own dump file\n", + "- The difference between a pass name and a dump name, which costs everybody an hour once\n", + "- Switching one of GCC's passes off from outside, and the assembly moving because of it\n", + "- The pass that changes nothing when you switch it off, which is the more useful result\n", + "- What `gxplug` is doing, and the honest ratio between passes that run and passes that show\n", + "- Why the plugin API is not an API, and what that means for anything you write" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b05-02", + "metadata": {}, + "outputs": [], + "source": [ + "# Setup. Safe to run twice, and does nothing at all if you already have the repository.\n", + "#\n", + "# Colab starts with none of this project's files and a GCC that is several years older than\n", + "# the one the lessons were written against. This cell fixes the first problem by cloning,\n", + "# and works around the second by having gxray fall back to recorded dumps or to Compiler\n", + "# Explorer, both of which are really GCC 16.\n", + "import os\n", + "import subprocess\n", + "import sys\n", + "\n", + "REPO = \"https://github.com/tamnd/gcc-internals\"\n", + "\n", + "if \"google.colab\" in sys.modules:\n", + " if not os.path.isdir(\"gcc-internals\"):\n", + " subprocess.run([\"git\", \"clone\", \"--depth\", \"1\", \"-q\", REPO], check=True)\n", + " if os.path.basename(os.getcwd()) != \"gcc-internals\":\n", + " os.chdir(\"gcc-internals\")\n", + " if os.getcwd() not in sys.path:\n", + " sys.path.insert(0, os.getcwd())\n", + "\n", + "import gxray\n", + "\n", + "print(\"gxray\", gxray.__version__, \"on python\", sys.version.split()[0])" + ] + }, + { + "cell_type": "markdown", + "id": "b05-03", + "metadata": {}, + "source": [ + "## What was recorded, and by what\n", + "\n", + "Everything printed below came out of one recording run on a machine with a real GCC. The\n", + "compiler is the same release as the tree every citation in this book points at, so a line\n", + "number here and a line number in `vendor/gcc` are the same line." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b05-04", + "metadata": {}, + "outputs": [], + "source": [ + "from gxray import plug\n", + "\n", + "session = plug.load_session()\n", + "\n", + "print(session.compiler)\n", + "print(session.target)\n", + "print(f\"recorded {session.recorded}, {len(session.invocations)} compilations\")\n", + "print()\n", + "print(session.probe)" + ] + }, + { + "cell_type": "markdown", + "id": "b05-05", + "metadata": {}, + "source": [ + "That probe output is the `Makefile` saying what it found. Three lines of it are the whole\n", + "reason `gxplug/Makefile` is ninety lines and not five: where this compiler keeps its plugin\n", + "headers, whether `gmp.h` is on the include path, and what the linker needs on this platform.\n", + "None of the three is the same on Debian, Fedora, Homebrew and a compiler you built yourself.\n", + "\n", + "## The three things a plugin must have\n", + "\n", + "Here is the smallest one, with its opening comment stripped. Forty lines, and three of them\n", + "are not optional." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b05-06", + "metadata": {}, + "outputs": [], + "source": [ + "print(plug.body(\"hello\"))" + ] + }, + { + "cell_type": "markdown", + "id": "b05-07", + "metadata": {}, + "source": [ + "Those three:\n", + "\n", + "`int plugin_is_GPL_compatible;` is a symbol GCC looks up by name. It is never read. Its\n", + "presence is the whole check.\n", + "\n", + "`plugin_init` is the entry point, found by name the same way. It gets the plugin's own\n", + "arguments and the compiler's version, and returning non-zero from it stops the compilation.\n", + "\n", + "The version check is the third, and it is the plugin's job rather than GCC's. GCC hands you\n", + "its version and expects you to compare it against yours. A plugin that skips the check is a\n", + "plugin that loads into anything and does undefined things there.\n", + "\n", + "Everything else is one call to `register_callback` per event you care about. Here is what\n", + "that plugin printed, compiling `l2.c`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b05-08", + "metadata": {}, + "outputs": [], + "source": [ + "hello = session[\"hello\"]\n", + "print(hello.command)\n", + "print()\n", + "for line in hello.said:\n", + " print(\" \", line)" + ] + }, + { + "cell_type": "markdown", + "id": "b05-09", + "metadata": {}, + "source": [ + "Two functions, then the total, and the arguments come through the same way." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b05-10", + "metadata": {}, + "outputs": [], + "source": [ + "print(session[\"hello-arg\"].command)\n", + "print()\n", + "for line in session[\"hello-arg\"].said:\n", + " print(\" \", line)" + ] + }, + { + "cell_type": "markdown", + "id": "b05-11", + "metadata": {}, + "source": [ + "`-fplugin-arg-hello-who=reader` is parsed by the driver into a key and a value and handed to\n", + "`plugin_init` in `info->argv`. The plugin's name in the middle of that flag is the base name\n", + "of the shared object, which is how the compiler knows which of several loaded plugins the\n", + "argument is for, and which is why renaming your `.so` breaks every command line you wrote.\n", + "\n", + "And the promise all of this rests on:\n", + "\n", + "a plugin that only observes leaves the generated code byte for byte identical, which is checkable rather than assertable and is checked here for two of the five." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b05-12", + "metadata": {}, + "outputs": [], + "source": [ + "plain = session[\"plain\"]\n", + "for name in (\"hello\", \"countpass\"):\n", + " same = session[name].asm == plain.asm\n", + " print(f\"{name:<12} {'identical' if same else 'DIFFERENT'} to the no-plugin assembly\")\n", + "print()\n", + "print(f\"{len(plain.asm.splitlines())} lines of assembly, unchanged\")" + ] + }, + { + "cell_type": "markdown", + "id": "b05-13", + "metadata": {}, + "source": [ + "## The three ways a plugin is refused\n", + "\n", + "All three happen before the plugin runs a line of its own code, or in one case immediately\n", + "after, and all three stop the compilation. None of them is a warning.\n", + "\n", + "The first is the licence symbol, at [`gcc/plugin.cc:713@releases/gcc-16.2.0`](https://github.com/gcc-mirror/gcc/blob/releases/gcc-16.2.0/gcc/plugin.cc#L713)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b05-14", + "metadata": {}, + "outputs": [], + "source": [ + "from gxray import source\n", + "\n", + "cuts = source.load_extract(\"b05\")\n", + "print(f\"{len(cuts)} excerpts, {cuts.lines()} lines, cut from {cuts.tag}\")\n", + "print()\n", + "shown = cuts[\"licence\"]\n", + "print(shown.about)\n", + "print(f\"{shown.span} ({shown.citation})\")\n", + "print()\n", + "print(shown.numbered({714: \"looked up by name, never called, never read\"}))" + ] + }, + { + "cell_type": "markdown", + "id": "b05-15", + "metadata": {}, + "source": [ + "`fatal_error` and not `error`, so the compilation stops there and then. `nolicence.cc` is\n", + "`hello.cc` with that one line deleted, and this is what it gets." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b05-16", + "metadata": {}, + "outputs": [], + "source": [ + "one = session[\"nolicence\"]\n", + "print(f\"returncode {one.returncode}\")\n", + "print()\n", + "for line in one.said:\n", + " print(\" \", line[:110])" + ] + }, + { + "cell_type": "markdown", + "id": "b05-17", + "metadata": {}, + "source": [ + "The plugin's own first line of output is not there, because it never ran.\n", + "\n", + "The second refusal is the version, and it is the one that will happen to you. The check is at\n", + "[`gcc/plugin.cc:1004@releases/gcc-16.2.0`](https://github.com/gcc-mirror/gcc/blob/releases/gcc-16.2.0/gcc/plugin.cc#L1004), and the interesting part is how much it\n", + "compares." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b05-18", + "metadata": {}, + "outputs": [], + "source": [ + "shown = cuts[\"version\"]\n", + "print(shown.about)\n", + "print(f\"{shown.span} ({shown.citation})\")\n", + "print()\n", + "notes = {\n", + " 1013: \"the version, which is the field everybody expects\",\n", + " 1015: \"and the datestamp\",\n", + " 1021: \"and the entire configure command line\",\n", + "}\n", + "print(shown.numbered(notes))" + ] + }, + { + "cell_type": "markdown", + "id": "b05-19", + "metadata": {}, + "source": [ + "the default version check compares five fields including the full configure argument string, so two builds of the same GCC release configured differently have incompatible plugin ABIs and refuse each other's plugins.\n", + "\n", + "That last field is the one that matters. `--enable-checking` changes struct layouts.\n", + "`--enable-languages` changes which trees exist. A plugin built against Debian's GCC 16.2 and\n", + "loaded into Fedora's GCC 16.2 is loading into a different compiler, and the string comparison\n", + "is the only thing standing between that and a crash with no explanation.\n", + "\n", + "`wrongver.cc` claims to have been built by GCC 15.1.0 so the failure can be shown without\n", + "needing two compilers installed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b05-20", + "metadata": {}, + "outputs": [], + "source": [ + "one = session[\"wrongver\"]\n", + "print(f\"returncode {one.returncode}\")\n", + "print()\n", + "for line in one.said:\n", + " print(\" \", line)" + ] + }, + { + "cell_type": "markdown", + "id": "b05-21", + "metadata": {}, + "source": [ + "Three lines, from three different places. The first is the plugin's own `error` call, the\n", + "second is its `inform`, and the third is GCC at\n", + "[`gcc/plugin.cc:731@releases/gcc-16.2.0`](https://github.com/gcc-mirror/gcc/blob/releases/gcc-16.2.0/gcc/plugin.cc#L731) reacting to a non-zero return." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b05-22", + "metadata": {}, + "outputs": [], + "source": [ + "shown = cuts[\"initcall\"]\n", + "print(shown.about)\n", + "print(f\"{shown.span} ({shown.citation})\")\n", + "print()\n", + "notes = {\n", + " 732: \"your plugin_init, and its return value is the whole protocol\",\n", + " 738: \"the handle is leaked on purpose, because unloading mid-compilation is worse\",\n", + "}\n", + "print(shown.numbered(notes))" + ] + }, + { + "cell_type": "markdown", + "id": "b05-23", + "metadata": {}, + "source": [ + "The third refusal is the simplest and the most common: the file is not there. That is a\n", + "`dlopen` failure and not a driver error, which is why the message is a wall of paths, and\n", + "which is why `-fplugin=gxplug.so` without the `./` fails on Linux and works on macOS." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b05-24", + "metadata": {}, + "outputs": [], + "source": [ + "print(session[\"missing\"].said[0][:150], \"...\")\n", + "print()\n", + "print(cuts[\"dlopen\"].numbered({703: \"RTLD_NOW, so a symbol it needs and cannot get fails here\"}))" + ] + }, + { + "cell_type": "markdown", + "id": "b05-25", + "metadata": {}, + "source": [ + "`RTLD_NOW` is deliberate and the comment says why. Binding every symbol at load time means an\n", + "ABI mismatch that got past the version check shows up as a failed `dlopen` rather than as a\n", + "crash four hundred passes later.\n", + "\n", + "There is a fourth way to be refused that is nobody's fault but yours, and it is worth a line\n", + "because the alternative is worse." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b05-26", + "metadata": {}, + "outputs": [], + "source": [ + "print(session[\"badarg\"].command)\n", + "print()\n", + "for line in session[\"badarg\"].said:\n", + " print(\" \", line)" + ] + }, + { + "cell_type": "markdown", + "id": "b05-27", + "metadata": {}, + "source": [ + "A mistyped `-fplugin-arg-` name is not a diagnostic GCC can produce, because GCC has no idea\n", + "what arguments your plugin takes. It hands over whatever it was given. So a plugin that\n", + "ignores what it does not recognize is a plugin where `-fplugin-arg-gxplug-outt=/tmp/x` does\n", + "nothing for an hour, and `gxplug` refuses instead.\n", + "\n", + "## What an event actually is\n", + "\n", + "The list of [events](https://github.com/tamnd/gcc-internals/blob/main/GLOSSARY.md#plugin-event) is a file of one-line macro calls at\n", + "[`gcc/plugin.def:20@releases/gcc-16.2.0`](https://github.com/gcc-mirror/gcc/blob/releases/gcc-16.2.0/gcc/plugin.def#L20)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b05-28", + "metadata": {}, + "outputs": [], + "source": [ + "shown = cuts[\"defevent\"]\n", + "print(shown.about)\n", + "print(f\"{shown.span} ({shown.citation})\")\n", + "print()\n", + "print(shown.numbered({27: \"not an event, despite being in the list of events\"}))" + ] + }, + { + "cell_type": "markdown", + "id": "b05-29", + "metadata": {}, + "source": [ + "Twenty six of these. The order of the file is the ABI, because the enumerator's value is the\n", + "index into the callback table, which is why a new event is always added at the end.\n", + "\n", + "Firing one is less than people imagine. It is a linked list walked in registration order, at\n", + "[`gcc/plugin.cc:582@releases/gcc-16.2.0`](https://github.com/gcc-mirror/gcc/blob/releases/gcc-16.2.0/gcc/plugin.cc#L582)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b05-30", + "metadata": {}, + "outputs": [], + "source": [ + "shown = cuts[\"dispatch\"]\n", + "print(shown.about)\n", + "print(f\"{shown.span} ({shown.citation})\")\n", + "print()\n", + "print(shown.numbered({590: \"no return value is read, so a callback cannot refuse anything\"}))" + ] + }, + { + "cell_type": "markdown", + "id": "b05-31", + "metadata": {}, + "source": [ + "Two plugins registered for the same event both run, in the order they were loaded, and\n", + "neither can see or stop the other. There is no priority and no veto.\n", + "\n", + "And then the part of the mechanism that is genuinely misleading. Three of the twenty six\n", + "names in that list are never fired at all. They are [pseudo-events](https://github.com/tamnd/gcc-internals/blob/main/GLOSSARY.md#pseudo-event),\n", + "acted on the moment you register for them, at\n", + "[`gcc/plugin.cc:450@releases/gcc-16.2.0`](https://github.com/gcc-mirror/gcc/blob/releases/gcc-16.2.0/gcc/plugin.cc#L450).\n", + "\n", + "registering a pass is spelled exactly like registering a callback, and is not one: PLUGIN_PASS_MANAGER_SETUP is handled inside register_callback and never fired, so the fourth argument is read immediately rather than handed back to you later." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b05-32", + "metadata": {}, + "outputs": [], + "source": [ + "shown = cuts[\"pseudo\"]\n", + "print(shown.about)\n", + "print(f\"{shown.span} ({shown.citation})\")\n", + "print()\n", + "notes = {\n", + " 459: \"the callback must be null, because there is nothing to call it from\",\n", + " 460: \"the user data is a register_pass_info, and it is used right here\",\n", + "}\n", + "print(shown.numbered(notes))" + ] + }, + { + "cell_type": "markdown", + "id": "b05-33", + "metadata": {}, + "source": [ + "This is the thing to know before writing your first pass, because the code you will copy from\n", + "a blog post looks like an event registration and behaves like a function call, and passing a\n", + "callback alongside the pass info trips a `gcc_assert` rather than producing a diagnostic.\n", + "\n", + "## A pass of your own\n", + "\n", + "Here is the whole of it. A GIMPLE pass that counts what it sees and changes nothing." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b05-34", + "metadata": {}, + "outputs": [], + "source": [ + "print(plug.body(\"countpass\"))" + ] + }, + { + "cell_type": "markdown", + "id": "b05-35", + "metadata": {}, + "source": [ + "Three parts.\n", + "\n", + "`pass_data` is what the pass says about itself. The two fields worth stopping on are\n", + "`properties_required`, which is `PROP_ssa` here and means the pass manager will refuse to run\n", + "this pass anywhere the function is not in SSA form, and `todo_flags_finish`, which is zero and\n", + "is a promise. See below.\n", + "\n", + "The class is a `gimple_opt_pass` with a `gate` and an `execute`. A gate returning false is\n", + "what `-fdump-passes` prints as `OFF`. `execute` gets the function and returns a\n", + "[TODO mask](https://github.com/tamnd/gcc-internals/blob/main/GLOSSARY.md#todo-flags).\n", + "\n", + "The registration is four fields, defined at\n", + "[`gcc/tree-pass.h:328@releases/gcc-16.2.0`](https://github.com/gcc-mirror/gcc/blob/releases/gcc-16.2.0/gcc/tree-pass.h#L328)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b05-36", + "metadata": {}, + "outputs": [], + "source": [ + "shown = cuts[\"info\"]\n", + "print(shown.about)\n", + "print(f\"{shown.span} ({shown.citation})\")\n", + "print()\n", + "notes = {\n", + " 338: \"the name of an existing pass, and it has to be the pass name\",\n", + " 340: \"which run of it, where 0 means every run and 1 means the first\",\n", + "}\n", + "print(shown.numbered(notes))" + ] + }, + { + "cell_type": "markdown", + "id": "b05-37", + "metadata": {}, + "source": [ + "[Where the pass lands](https://github.com/tamnd/gcc-internals/blob/main/GLOSSARY.md#pass-positioning) is decided by matching that name against\n", + "the pass list, at [`gcc/passes.cc:1377@releases/gcc-16.2.0`](https://github.com/gcc-mirror/gcc/blob/releases/gcc-16.2.0/gcc/passes.cc#L1377)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b05-38", + "metadata": {}, + "outputs": [], + "source": [ + "shown = cuts[\"position\"]\n", + "print(shown.about)\n", + "print(f\"{shown.span} ({shown.citation})\")\n", + "print()\n", + "notes = {\n", + " 1393: \"the type has to match too, so a GIMPLE pass cannot hang off an RTL one\",\n", + " 1399: \"instance 1 is not pass number 1, it is the one marked as first\",\n", + "}\n", + "print(shown.numbered(notes))" + ] + }, + { + "cell_type": "markdown", + "id": "b05-39", + "metadata": {}, + "source": [ + "A reference that matches nothing is a fatal error at registration time, which makes this the\n", + "one mistake in the whole area that tells you about itself immediately.\n", + "\n", + "So: does the pass exist? `-fdump-passes` prints the pipeline, and here is where the new one\n", + "landed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b05-40", + "metadata": {}, + "outputs": [], + "source": [ + "where = session.pipeline\n", + "print(f\"{where['total']} passes in the list, and the new one is number {where['index']}\")\n", + "print()\n", + "for line in where[\"around\"]:\n", + " mark = \" <-- yours\" if \"gxcount\" in line else \"\"\n", + " print(line + mark)" + ] + }, + { + "cell_type": "markdown", + "id": "b05-41", + "metadata": {}, + "source": [ + "a pass registered by a plugin is not a special case anywhere downstream: it appears in -fdump-passes between the two passes it was positioned against, and it gets its own numbered dump file under its own name, like every other pass.\n", + "\n", + "`tree-gxcount`, sitting between `tree-ssa` and `tree-walloca1`. It also gets\n", + "`-fdump-tree-gxcount` and a file called something like `l2.c.376t.gxcount`, with the same\n", + "numbering scheme every other dump file uses, because from here down nothing knows or cares\n", + "that this pass came from outside.\n", + "\n", + "And it ran." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b05-42", + "metadata": {}, + "outputs": [], + "source": [ + "counted = session[\"countpass\"]\n", + "print(counted.command)\n", + "print()\n", + "for line in counted.said:\n", + " print(\" \", line)\n", + "print()\n", + "print(\"assembly unchanged:\", counted.asm == session[\"plain\"].asm)" + ] + }, + { + "cell_type": "markdown", + "id": "b05-43", + "metadata": {}, + "source": [ + "Six phi nodes in `nearest`, which is the number T05 spent a lesson on, arrived at from inside\n", + "the compiler this time rather than by reading a dump.\n", + "\n", + "The zero this pass returns is the promise mentioned above. A pass that modifies the IR and\n", + "returns zero has left the pass manager believing things that are no longer true: the CFG is\n", + "clean, SSA is up to date, nothing needs verifying. The compilation then fails somewhere\n", + "several passes later, in code that did nothing wrong. It is the most expensive mistake a\n", + "first plugin can make and the hardest to attribute, and the reason this one only reads.\n", + "\n", + "## Switching one of GCC's passes off\n", + "\n", + "Every pass has a gate, and one event is handed the address of the answer rather than the\n", + "answer, at [`gcc/passes.cc:2597@releases/gcc-16.2.0`](https://github.com/gcc-mirror/gcc/blob/releases/gcc-16.2.0/gcc/passes.cc#L2597)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b05-44", + "metadata": {}, + "outputs": [], + "source": [ + "shown = cuts[\"gatecall\"]\n", + "print(shown.about)\n", + "print(f\"{shown.span} ({shown.citation})\")\n", + "print()\n", + "notes = {\n", + " 2597: \"which pass is being asked is not in the data, it is in this global\",\n", + " 2605: \"the only event whose data is written rather than read\",\n", + "}\n", + "print(shown.numbered(notes))" + ] + }, + { + "cell_type": "markdown", + "id": "b05-45", + "metadata": {}, + "source": [ + "So a plugin can decide, per pass and per function, whether one of GCC's own passes runs. No\n", + "flag exists for most of them." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b05-46", + "metadata": {}, + "outputs": [], + "source": [ + "print(plug.body(\"gate\"))" + ] + }, + { + "cell_type": "markdown", + "id": "b05-47", + "metadata": {}, + "source": [ + "a plugin can switch off one of GCC's own optimization passes for one function at a time, with no patch and no command line flag, and the generated code changes because of it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b05-48", + "metadata": {}, + "outputs": [], + "source": [ + "gated = session[\"gated\"]\n", + "print(gated.command)\n", + "print()\n", + "print(\" \", gated.said[-1])\n", + "print()\n", + "diff = session.diff(\"plain\", \"gated\")\n", + "print(f\"{len(diff)} lines of unified diff against the same program with nothing switched off\")\n", + "print()\n", + "print(\"\\n\".join(diff[:22]))" + ] + }, + { + "cell_type": "markdown", + "id": "b05-49", + "metadata": {}, + "source": [ + "`ivopts` is induction variable optimization: it rewrites a loop's array indexing into pointer\n", + "arithmetic the target can address directly. Switching it off leaves the loop correct and\n", + "slower, and it moves half the loop head. Fifty six lines of diff on a sixty nine line file.\n", + "\n", + "Now the more useful half of the same experiment." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b05-50", + "metadata": {}, + "outputs": [], + "source": [ + "ungated = session[\"ungated\"]\n", + "print(ungated.command)\n", + "print()\n", + "print(\" \", ungated.said[-1])\n", + "print()\n", + "print(\"assembly changed:\", ungated.asm != session[\"plain\"].asm)" + ] + }, + { + "cell_type": "markdown", + "id": "b05-51", + "metadata": {}, + "source": [ + "Early inlining was gated twice, refused twice, and the output is byte for byte what it was.\n", + "`l2.c` has a static function that clearly wants inlining and it still got inlined, because\n", + "`einline` is not the inliner that matters here: the IPA inliner later in the pipeline does the\n", + "work, and `einline` exists to make the early passes see through trivial calls.\n", + "\n", + "That result is worth more than the first one. A pass switched off with no visible effect is\n", + "the normal case, and the reason is almost always that something else does the same job.\n", + "\n", + "One thing will cost you an hour if nobody says it.\n", + "\n", + "the name in -fdump-tree-cddce1 is not a pass name: it is the pass called cddce plus the instance number the pass manager gave it, so asking for cddce1 by name matches nothing at all and reports nothing." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b05-52", + "metadata": {}, + "outputs": [], + "source": [ + "print(session[\"gate-dumpname\"].command)\n", + "print()\n", + "print(\" \", session[\"gate-dumpname\"].said[-1])" + ] + }, + { + "cell_type": "markdown", + "id": "b05-53", + "metadata": {}, + "source": [ + "Zero refusals, no error, no warning, and a compilation that looks exactly like a successful\n", + "experiment. The pass name is `cddce` and the trailing digit is the instance. This is the same\n", + "distinction that `ref_pass_instance_number` exists for, seen from the other side, and it is\n", + "why the plugin above prints a count rather than trusting that it did something.\n", + "\n", + "## What gxplug is doing\n", + "\n", + "`gxplug` is the plugin this repository ships and every lesson with a pass tape in it is built\n", + "on. It is one callback on `PLUGIN_PASS_EXECUTION`, two more on the ends of the run, and a\n", + "`PLUGIN_INFO` registration so `--help=plugin` says who it is. It writes one JSON object per\n", + "pass rather than printing anything." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b05-54", + "metadata": {}, + "outputs": [], + "source": [ + "stream = session.stream\n", + "runs = stream.runs\n", + "print(f\"{len(stream.events)} events, {len(runs)} pass runs, on {stream.functions}\")\n", + "print(f\"{stream.seconds:.4f} seconds of pass time recorded\")\n", + "print()\n", + "for one in runs[:6]:\n", + " print(f\" {one.name:<16} {one.start.size} -> {one.end.size if one.end else None}\")" + ] + }, + { + "cell_type": "markdown", + "id": "b05-55", + "metadata": {}, + "source": [ + "And the number the pass tape lessons rest on.\n", + "\n", + "most passes that run leave nothing measurable behind: fewer than one in four of the runs on a nine line program change the statement count, the insn count, the block count or the property bits." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b05-56", + "metadata": {}, + "outputs": [], + "source": [ + "marked = [one for one in runs if one.changed]\n", + "print(f\"{len(runs)} runs, {len(marked)} left a mark you can see from outside\")\n", + "print(f\"that is {100 * len(marked) // len(runs)}%\")\n", + "print()\n", + "for one in marked[:8]:\n", + " print(\n", + " f\" {one.name:<16} {one.start.size} -> {one.end.size} blocks \"\n", + " f\"{one.start.blocks} -> {one.end.blocks}\"\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "b05-57", + "metadata": {}, + "source": [ + "Read that carefully, because it is easy to draw the wrong conclusion from it. `changed` here\n", + "means one of four counts moved. A pass that rewrote an expression in place without changing\n", + "the number of statements reports `False` and did real work. What the ratio is good for is the\n", + "honest version of the sentence people say about GCC having four hundred passes: they do not\n", + "all do something to your function, and on a program this small most of them look at it and\n", + "leave.\n", + "\n", + "The one place the counts jump is the boundary between the two IRs." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b05-58", + "metadata": {}, + "outputs": [], + "source": [ + "where = next(i for i, one in enumerate(runs) if one.name == \"expand\")\n", + "for one in runs[where - 1 : where + 2]:\n", + " end = one.end\n", + " print(f\" {one.name:<12} statements {end.statements} insns {end.insns} blocks {end.blocks}\")" + ] + }, + { + "cell_type": "markdown", + "id": "b05-59", + "metadata": {}, + "source": [ + "Before `expand` the statement count is a number and the insn count is `null`. After it, the\n", + "other way round. They are never both set, because a basic block holds either a GIMPLE\n", + "sequence or an RTL insn chain in a union, and walking it as the wrong one does not fail a\n", + "check: it reads whichever pointer is there and segfaults. Which is what the first version of\n", + "`gxplug` did.\n", + "\n", + "## Why the plugin API is not an API\n", + "\n", + "Everything above works. None of it is a promise.\n", + "\n", + "`gcc-plugin.h` is not an interface designed for you. It is a small header that pulls in\n", + "GCC's own private headers, and what you are compiling against is the compiler's internals as\n", + "they happened to be on the day of that release. There is no stability commitment, no\n", + "deprecation cycle, and no list of what is safe to call. `tree.h` changes between releases\n", + "because a GCC developer needed it to.\n", + "\n", + "The consequences are concrete and you have seen most of them already:\n", + "\n", + "- The plugin has to be built by the compiler that will load it. Not the same version, the\n", + " same build. [The check](https://github.com/tamnd/gcc-internals/blob/main/GLOSSARY.md#plugin-abi) compares the configure line.\n", + "- No binary plugin can be distributed. Every plugin ships as source and is built on the\n", + " target machine, which is why every distribution has a separate `-plugin-dev` package.\n", + "- `-fno-rtti` is not a style choice. GCC is built without RTTI and a plugin that disagrees\n", + " gets link errors about typeinfo that name no cause.\n", + "- What arrives with an event is a `void *` whose real type is written in the call site and\n", + " nowhere else. Casting it wrong is a segfault, and `BP-PLUGIN` section 2 is a table of the\n", + " twenty nine call sites for exactly this reason.\n", + "- A pass you insert can be moved by a GCC release that reorders the pipeline, and your\n", + " `reference_pass_name` will still match, just somewhere else.\n", + "\n", + "The upgrade story is that there is no upgrade story. A plugin is pinned to a compiler, and\n", + "the honest way to maintain one is to test it against each release and fix what broke.\n", + "\n", + "Which is the whole argument for this repository's own design. `gxplug` emits JSON and the\n", + "Python side never parses a dump, so the fragile part is sixty lines of C++ in one file that\n", + "either compiles against a given GCC or does not, and the four hundred lines that read its\n", + "output do not care which compiler produced them.\n", + "\n", + "## Doing this yourself\n", + "\n", + "If you have a compiler with its plugin headers, from B01 or from a container, everything\n", + "above is four commands.\n", + "\n", + "```sh\n", + "make -C gxplug probe GCC=gcc-16 # what it found, and where\n", + "make -C gxplug GCC=gcc-16 # the plugin\n", + "make -C gxplug examples GCC=gcc-16 # the five in this lesson\n", + "make -C gxplug examples-check GCC=gcc-16\n", + "```\n", + "\n", + "The last one is the interesting one. It builds all five, loads each, and asserts that `hello`\n", + "and `countpass` leave the assembly identical, that `gate` does not, and that the two broken\n", + "ones are refused. The same target runs in CI on Debian, Fedora, Homebrew and a from-source\n", + "GCC on every push that touches the directory, which is what stops this lesson from describing\n", + "plugins that only work on the machine they were written on.\n", + "\n", + "The prerequisite is the plugin development package, which is a separate install almost\n", + "everywhere:\n", + "\n", + "| Channel | What to install |\n", + "|---|---|\n", + "| Debian | `gcc-16` and `gcc-16-plugin-dev` |\n", + "| Fedora | `gcc` and `gcc-plugin-devel` |\n", + "| Homebrew | `gcc`, which includes them |\n", + "| source | nothing, `make install` puts them under the prefix |\n", + "\n", + "Then point `-fplugin=` at a `.so` and give it a `./` if it is in the current directory.\n", + "\n", + "## Boss fight\n", + "\n", + "Eight questions about loading, events, passes and gates.\n", + "\n", + " python lessons/b05-the-plugin/grade.py\n", + "\n", + "Three of them are about failures rather than successes, which is where the time goes. The\n", + "last one has an answer that is a number, and the number is not the one most people say.\n", + "\n", + "## What to read next\n", + "\n", + "This is the end of M2. You can build GCC, bootstrap it, run it under a debugger, run its test\n", + "suite, and load code of your own into it. Everything from here on is about what the compiler\n", + "does rather than how to get at it.\n", + "\n", + "M3 is GIMPLE properly: the twenty odd statement codes, what a `GIMPLE_ASSIGN` really holds,\n", + "how the CFG is built and maintained, and the point in the pipeline where a function stops\n", + "being a tree and starts being a program." + ] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/lessons/b05-the-plugin/build.py b/lessons/b05-the-plugin/build.py new file mode 100644 index 0000000..0d83074 --- /dev/null +++ b/lessons/b05-the-plugin/build.py @@ -0,0 +1,737 @@ +"""B05. Sixty lines of C++, and you are inside the compiler. + +The last lesson of M2, and the one that turns a reader from somebody who watches GCC into +somebody who runs code inside it. Five plugins, all of them in `gxplug/examples/`, all of +them built and loaded on four different compilers by the `plugin` workflow on every push: + + hello the contract, in the smallest form that still works + countpass a GIMPLE pass of the reader's own, inserted after `ssa` + gate the same mechanism used to switch one of GCC's own passes off + nolicence refused for a missing symbol + wrongver refused by its own version check + +None of them can be built in a notebook, which is the constraint that shapes the lesson. A +plugin needs GCC's private headers and the same compiler that will load it, and a Colab +reader has neither. So the sources are in the repository where they can be read line by +line, and `record.py` next door holds eleven real compilations by a real GCC 16.2 with every +byte of output kept. + +The idea the lesson is built around is in its last section but decided in its first: the +plugin API is not an API. There is no stable interface, no header that is meant for you, and +a version check that compares the configure line because two builds of the same release have +incompatible layouts. Everything that is hard about writing a plugin follows from that, and +so does the one thing that makes plugins worth having, which is that you are inside, holding +the real IR, with nothing translated. +""" + +from tools.nbbuild import Lesson + +lesson = Lesson( + "b05-the-plugin", + "b05", + title="Sixty lines of C++, and you are inside the compiler", + milestone="M2", + summary=( + "GCC's plugin mechanism from the outside in: the three things a plugin has to have, " + "the three ways it is refused, what an event actually is, a GIMPLE pass of your own " + "inserted after ssa with its own dump file, switching one of GCC's passes off from " + "outside and watching the assembly move, and why the thing you are writing against " + "is not an API" + ), +) +badge = lesson.badge +cite = lesson.cite +term = lesson.term +claim = lesson.claim + + +lesson.md(f""" +# B05. Sixty lines of C++, and you are inside the compiler + +{badge} + +Four lessons of this milestone have been about getting a compiler and finding out what it +did. This one is about putting your own code inside it while it runs. + +A {term("plugin")} is a shared object that `cc1` loads at startup. Once it is in, it is in: +it holds the real trees, calls the real functions, sees the real IR between two real passes, +and segfaults the real compiler. There is no sandbox and no translation layer. That is both +the reason plugins are the right tool for a question the dump files cannot answer, and the +reason writing one feels like nothing else in this course. + +**You cannot build one here.** A plugin is C++ compiled against GCC's private headers, and it +has to be built by the compiler that will load it. Colab has neither the headers nor a +matching GCC. So this lesson works the way B03 did: the five plugins are in this repository +under `gxplug/examples/`, where every line of them can be read, and everything they printed +was recorded by a real GCC 16.2 and committed. + +If you have a compiler from B01 or a container, everything below is four commands away, and +the section before the boss fight says which four. + +**What you come away with** + +- The three things a plugin must have, and what happens when each one is missing +- Why the version check compares the configure line and not just the version +- What an event actually is, and the three names in the event list that are not events +- A pass of your own, running after `ssa`, in `-fdump-passes`, with its own dump file +- The difference between a pass name and a dump name, which costs everybody an hour once +- Switching one of GCC's passes off from outside, and the assembly moving because of it +- The pass that changes nothing when you switch it off, which is the more useful result +- What `gxplug` is doing, and the honest ratio between passes that run and passes that show +- Why the plugin API is not an API, and what that means for anything you write +""") + +lesson.setup() + +lesson.md(f""" +## What was recorded, and by what + +Everything printed below came out of one recording run on a machine with a real GCC. The +compiler is the same release as the tree every citation in this book points at, so a line +number here and a line number in `vendor/gcc` are the same line. +""") + +lesson.code(""" +from gxray import plug + +session = plug.load_session() + +print(session.compiler) +print(session.target) +print(f"recorded {session.recorded}, {len(session.invocations)} compilations") +print() +print(session.probe) +""") + +lesson.md(f""" +That probe output is the `Makefile` saying what it found. Three lines of it are the whole +reason `gxplug/Makefile` is ninety lines and not five: where this compiler keeps its plugin +headers, whether `gmp.h` is on the include path, and what the linker needs on this platform. +None of the three is the same on Debian, Fedora, Homebrew and a compiler you built yourself. + +## The three things a plugin must have + +Here is the smallest one, with its opening comment stripped. Forty lines, and three of them +are not optional. +""") + +lesson.code(""" +print(plug.body("hello")) +""") + +lesson.md(f""" +Those three: + +`int plugin_is_GPL_compatible;` is a symbol GCC looks up by name. It is never read. Its +presence is the whole check. + +`plugin_init` is the entry point, found by name the same way. It gets the plugin's own +arguments and the compiler's version, and returning non-zero from it stops the compilation. + +The version check is the third, and it is the plugin's job rather than GCC's. GCC hands you +its version and expects you to compare it against yours. A plugin that skips the check is a +plugin that loads into anything and does undefined things there. + +Everything else is one call to `register_callback` per event you care about. Here is what +that plugin printed, compiling `l2.c`. +""") + +lesson.code(""" +hello = session["hello"] +print(hello.command) +print() +for line in hello.said: + print(" ", line) +""") + +lesson.md(f""" +Two functions, then the total, and the arguments come through the same way. +""") + +lesson.code(""" +print(session["hello-arg"].command) +print() +for line in session["hello-arg"].said: + print(" ", line) +""") + +lesson.md(f""" +`-fplugin-arg-hello-who=reader` is parsed by the driver into a key and a value and handed to +`plugin_init` in `info->argv`. The plugin's name in the middle of that flag is the base name +of the shared object, which is how the compiler knows which of several loaded plugins the +argument is for, and which is why renaming your `.so` breaks every command line you wrote. + +And the promise all of this rests on: + +{ + claim( + "a plugin that only observes leaves the generated code byte for byte identical, " + "which is checkable rather than assertable and is checked here for two of the five" + ) +}. +""") + +lesson.code(""" +plain = session["plain"] +for name in ("hello", "countpass"): + same = session[name].asm == plain.asm + print(f"{name:<12} {'identical' if same else 'DIFFERENT'} to the no-plugin assembly") +print() +print(f"{len(plain.asm.splitlines())} lines of assembly, unchanged") +""") + +lesson.md(f""" +## The three ways a plugin is refused + +All three happen before the plugin runs a line of its own code, or in one case immediately +after, and all three stop the compilation. None of them is a warning. + +The first is the licence symbol, at {cite("gcc/plugin.cc:713@releases/gcc-16.2.0")}. +""") + +lesson.code(""" +from gxray import source + +cuts = source.load_extract("b05") +print(f"{len(cuts)} excerpts, {cuts.lines()} lines, cut from {cuts.tag}") +print() +shown = cuts["licence"] +print(shown.about) +print(f"{shown.span} ({shown.citation})") +print() +print(shown.numbered({714: "looked up by name, never called, never read"})) +""") + +lesson.md(f""" +`fatal_error` and not `error`, so the compilation stops there and then. `nolicence.cc` is +`hello.cc` with that one line deleted, and this is what it gets. +""") + +lesson.code(""" +one = session["nolicence"] +print(f"returncode {one.returncode}") +print() +for line in one.said: + print(" ", line[:110]) +""") + +lesson.md(f""" +The plugin's own first line of output is not there, because it never ran. + +The second refusal is the version, and it is the one that will happen to you. The check is at +{cite("gcc/plugin.cc:1004@releases/gcc-16.2.0")}, and the interesting part is how much it +compares. +""") + +lesson.code(""" +shown = cuts["version"] +print(shown.about) +print(f"{shown.span} ({shown.citation})") +print() +notes = { + 1013: "the version, which is the field everybody expects", + 1015: "and the datestamp", + 1021: "and the entire configure command line", +} +print(shown.numbered(notes)) +""") + +lesson.md(f""" +{ + claim( + "the default version check compares five fields including the full configure " + "argument string, so two builds of the same GCC release configured differently have " + "incompatible plugin ABIs and refuse each other's plugins" + ) +}. + +That last field is the one that matters. `--enable-checking` changes struct layouts. +`--enable-languages` changes which trees exist. A plugin built against Debian's GCC 16.2 and +loaded into Fedora's GCC 16.2 is loading into a different compiler, and the string comparison +is the only thing standing between that and a crash with no explanation. + +`wrongver.cc` claims to have been built by GCC 15.1.0 so the failure can be shown without +needing two compilers installed. +""") + +lesson.code(""" +one = session["wrongver"] +print(f"returncode {one.returncode}") +print() +for line in one.said: + print(" ", line) +""") + +lesson.md(f""" +Three lines, from three different places. The first is the plugin's own `error` call, the +second is its `inform`, and the third is GCC at +{cite("gcc/plugin.cc:731@releases/gcc-16.2.0")} reacting to a non-zero return. +""") + +lesson.code(""" +shown = cuts["initcall"] +print(shown.about) +print(f"{shown.span} ({shown.citation})") +print() +notes = { + 732: "your plugin_init, and its return value is the whole protocol", + 738: "the handle is leaked on purpose, because unloading mid-compilation is worse", +} +print(shown.numbered(notes)) +""") + +lesson.md(f""" +The third refusal is the simplest and the most common: the file is not there. That is a +`dlopen` failure and not a driver error, which is why the message is a wall of paths, and +which is why `-fplugin=gxplug.so` without the `./` fails on Linux and works on macOS. +""") + +lesson.code(""" +print(session["missing"].said[0][:150], "...") +print() +print(cuts["dlopen"].numbered({703: "RTLD_NOW, so a symbol it needs and cannot get fails here"})) +""") + +lesson.md(f""" +`RTLD_NOW` is deliberate and the comment says why. Binding every symbol at load time means an +ABI mismatch that got past the version check shows up as a failed `dlopen` rather than as a +crash four hundred passes later. + +There is a fourth way to be refused that is nobody's fault but yours, and it is worth a line +because the alternative is worse. +""") + +lesson.code(""" +print(session["badarg"].command) +print() +for line in session["badarg"].said: + print(" ", line) +""") + +lesson.md(f""" +A mistyped `-fplugin-arg-` name is not a diagnostic GCC can produce, because GCC has no idea +what arguments your plugin takes. It hands over whatever it was given. So a plugin that +ignores what it does not recognize is a plugin where `-fplugin-arg-gxplug-outt=/tmp/x` does +nothing for an hour, and `gxplug` refuses instead. + +## What an event actually is + +The list of {term("plugin event", "events")} is a file of one-line macro calls at +{cite("gcc/plugin.def:20@releases/gcc-16.2.0")}. +""") + +lesson.code(""" +shown = cuts["defevent"] +print(shown.about) +print(f"{shown.span} ({shown.citation})") +print() +print(shown.numbered({27: "not an event, despite being in the list of events"})) +""") + +lesson.md(f""" +Twenty six of these. The order of the file is the ABI, because the enumerator's value is the +index into the callback table, which is why a new event is always added at the end. + +Firing one is less than people imagine. It is a linked list walked in registration order, at +{cite("gcc/plugin.cc:582@releases/gcc-16.2.0")}. +""") + +lesson.code(""" +shown = cuts["dispatch"] +print(shown.about) +print(f"{shown.span} ({shown.citation})") +print() +print(shown.numbered({590: "no return value is read, so a callback cannot refuse anything"})) +""") + +lesson.md(f""" +Two plugins registered for the same event both run, in the order they were loaded, and +neither can see or stop the other. There is no priority and no veto. + +And then the part of the mechanism that is genuinely misleading. Three of the twenty six +names in that list are never fired at all. They are {term("pseudo-event", "pseudo-events")}, +acted on the moment you register for them, at +{cite("gcc/plugin.cc:450@releases/gcc-16.2.0")}. + +{ + claim( + "registering a pass is spelled exactly like registering a callback, and is not one: " + "PLUGIN_PASS_MANAGER_SETUP is handled inside register_callback and never fired, so " + "the fourth argument is read immediately rather than handed back to you later" + ) +}. +""") + +lesson.code(""" +shown = cuts["pseudo"] +print(shown.about) +print(f"{shown.span} ({shown.citation})") +print() +notes = { + 459: "the callback must be null, because there is nothing to call it from", + 460: "the user data is a register_pass_info, and it is used right here", +} +print(shown.numbered(notes)) +""") + +lesson.md(f""" +This is the thing to know before writing your first pass, because the code you will copy from +a blog post looks like an event registration and behaves like a function call, and passing a +callback alongside the pass info trips a `gcc_assert` rather than producing a diagnostic. + +## A pass of your own + +Here is the whole of it. A GIMPLE pass that counts what it sees and changes nothing. +""") + +lesson.code(""" +print(plug.body("countpass")) +""") + +lesson.md(f""" +Three parts. + +`pass_data` is what the pass says about itself. The two fields worth stopping on are +`properties_required`, which is `PROP_ssa` here and means the pass manager will refuse to run +this pass anywhere the function is not in SSA form, and `todo_flags_finish`, which is zero and +is a promise. See below. + +The class is a `gimple_opt_pass` with a `gate` and an `execute`. A gate returning false is +what `-fdump-passes` prints as `OFF`. `execute` gets the function and returns a +{term("TODO flags", "TODO mask")}. + +The registration is four fields, defined at +{cite("gcc/tree-pass.h:328@releases/gcc-16.2.0")}. +""") + +lesson.code(""" +shown = cuts["info"] +print(shown.about) +print(f"{shown.span} ({shown.citation})") +print() +notes = { + 338: "the name of an existing pass, and it has to be the pass name", + 340: "which run of it, where 0 means every run and 1 means the first", +} +print(shown.numbered(notes)) +""") + +lesson.md(f""" +{term("pass positioning", "Where the pass lands")} is decided by matching that name against +the pass list, at {cite("gcc/passes.cc:1377@releases/gcc-16.2.0")}. +""") + +lesson.code(""" +shown = cuts["position"] +print(shown.about) +print(f"{shown.span} ({shown.citation})") +print() +notes = { + 1393: "the type has to match too, so a GIMPLE pass cannot hang off an RTL one", + 1399: "instance 1 is not pass number 1, it is the one marked as first", +} +print(shown.numbered(notes)) +""") + +lesson.md(f""" +A reference that matches nothing is a fatal error at registration time, which makes this the +one mistake in the whole area that tells you about itself immediately. + +So: does the pass exist? `-fdump-passes` prints the pipeline, and here is where the new one +landed. +""") + +lesson.code(""" +where = session.pipeline +print(f"{where['total']} passes in the list, and the new one is number {where['index']}") +print() +for line in where["around"]: + mark = " <-- yours" if "gxcount" in line else "" + print(line + mark) +""") + +lesson.md(f""" +{ + claim( + "a pass registered by a plugin is not a special case anywhere downstream: it appears " + "in -fdump-passes between the two passes it was positioned against, and it gets its " + "own numbered dump file under its own name, like every other pass" + ) +}. + +`tree-gxcount`, sitting between `tree-ssa` and `tree-walloca1`. It also gets +`-fdump-tree-gxcount` and a file called something like `l2.c.376t.gxcount`, with the same +numbering scheme every other dump file uses, because from here down nothing knows or cares +that this pass came from outside. + +And it ran. +""") + +lesson.code(""" +counted = session["countpass"] +print(counted.command) +print() +for line in counted.said: + print(" ", line) +print() +print("assembly unchanged:", counted.asm == session["plain"].asm) +""") + +lesson.md(f""" +Six phi nodes in `nearest`, which is the number T05 spent a lesson on, arrived at from inside +the compiler this time rather than by reading a dump. + +The zero this pass returns is the promise mentioned above. A pass that modifies the IR and +returns zero has left the pass manager believing things that are no longer true: the CFG is +clean, SSA is up to date, nothing needs verifying. The compilation then fails somewhere +several passes later, in code that did nothing wrong. It is the most expensive mistake a +first plugin can make and the hardest to attribute, and the reason this one only reads. + +## Switching one of GCC's passes off + +Every pass has a gate, and one event is handed the address of the answer rather than the +answer, at {cite("gcc/passes.cc:2597@releases/gcc-16.2.0")}. +""") + +lesson.code(""" +shown = cuts["gatecall"] +print(shown.about) +print(f"{shown.span} ({shown.citation})") +print() +notes = { + 2597: "which pass is being asked is not in the data, it is in this global", + 2605: "the only event whose data is written rather than read", +} +print(shown.numbered(notes)) +""") + +lesson.md(f""" +So a plugin can decide, per pass and per function, whether one of GCC's own passes runs. No +flag exists for most of them. +""") + +lesson.code(""" +print(plug.body("gate")) +""") + +lesson.md(f""" +{ + claim( + "a plugin can switch off one of GCC's own optimization passes for one function at a " + "time, with no patch and no command line flag, and the generated code changes " + "because of it" + ) +}. +""") + +lesson.code(""" +gated = session["gated"] +print(gated.command) +print() +print(" ", gated.said[-1]) +print() +diff = session.diff("plain", "gated") +print(f"{len(diff)} lines of unified diff against the same program with nothing switched off") +print() +print("\\n".join(diff[:22])) +""") + +lesson.md(f""" +`ivopts` is induction variable optimization: it rewrites a loop's array indexing into pointer +arithmetic the target can address directly. Switching it off leaves the loop correct and +slower, and it moves half the loop head. Fifty six lines of diff on a sixty nine line file. + +Now the more useful half of the same experiment. +""") + +lesson.code(""" +ungated = session["ungated"] +print(ungated.command) +print() +print(" ", ungated.said[-1]) +print() +print("assembly changed:", ungated.asm != session["plain"].asm) +""") + +lesson.md(f""" +Early inlining was gated twice, refused twice, and the output is byte for byte what it was. +`l2.c` has a static function that clearly wants inlining and it still got inlined, because +`einline` is not the inliner that matters here: the IPA inliner later in the pipeline does the +work, and `einline` exists to make the early passes see through trivial calls. + +That result is worth more than the first one. A pass switched off with no visible effect is +the normal case, and the reason is almost always that something else does the same job. + +One thing will cost you an hour if nobody says it. + +{ + claim( + "the name in -fdump-tree-cddce1 is not a pass name: it is the pass called cddce plus " + "the instance number the pass manager gave it, so asking for cddce1 by name matches " + "nothing at all and reports nothing" + ) +}. +""") + +lesson.code(""" +print(session["gate-dumpname"].command) +print() +print(" ", session["gate-dumpname"].said[-1]) +""") + +lesson.md(f""" +Zero refusals, no error, no warning, and a compilation that looks exactly like a successful +experiment. The pass name is `cddce` and the trailing digit is the instance. This is the same +distinction that `ref_pass_instance_number` exists for, seen from the other side, and it is +why the plugin above prints a count rather than trusting that it did something. + +## What gxplug is doing + +`gxplug` is the plugin this repository ships and every lesson with a pass tape in it is built +on. It is one callback on `PLUGIN_PASS_EXECUTION`, two more on the ends of the run, and a +`PLUGIN_INFO` registration so `--help=plugin` says who it is. It writes one JSON object per +pass rather than printing anything. +""") + +lesson.code(""" +stream = session.stream +runs = stream.runs +print(f"{len(stream.events)} events, {len(runs)} pass runs, on {stream.functions}") +print(f"{stream.seconds:.4f} seconds of pass time recorded") +print() +for one in runs[:6]: + print(f" {one.name:<16} {one.start.size} -> {one.end.size if one.end else None}") +""") + +lesson.md(f""" +And the number the pass tape lessons rest on. + +{ + claim( + "most passes that run leave nothing measurable behind: fewer than one in four of the " + "runs on a nine line program change the statement count, the insn count, the block " + "count or the property bits" + ) +}. +""") + +lesson.code(""" +marked = [one for one in runs if one.changed] +print(f"{len(runs)} runs, {len(marked)} left a mark you can see from outside") +print(f"that is {100 * len(marked) // len(runs)}%") +print() +for one in marked[:8]: + print( + f" {one.name:<16} {one.start.size} -> {one.end.size} blocks " + f"{one.start.blocks} -> {one.end.blocks}" + ) +""") + +lesson.md(f""" +Read that carefully, because it is easy to draw the wrong conclusion from it. `changed` here +means one of four counts moved. A pass that rewrote an expression in place without changing +the number of statements reports `False` and did real work. What the ratio is good for is the +honest version of the sentence people say about GCC having four hundred passes: they do not +all do something to your function, and on a program this small most of them look at it and +leave. + +The one place the counts jump is the boundary between the two IRs. +""") + +lesson.code(""" +where = next(i for i, one in enumerate(runs) if one.name == "expand") +for one in runs[where - 1 : where + 2]: + end = one.end + print(f" {one.name:<12} statements {end.statements} insns {end.insns} blocks {end.blocks}") +""") + +lesson.md(f""" +Before `expand` the statement count is a number and the insn count is `null`. After it, the +other way round. They are never both set, because a basic block holds either a GIMPLE +sequence or an RTL insn chain in a union, and walking it as the wrong one does not fail a +check: it reads whichever pointer is there and segfaults. Which is what the first version of +`gxplug` did. + +## Why the plugin API is not an API + +Everything above works. None of it is a promise. + +`gcc-plugin.h` is not an interface designed for you. It is a small header that pulls in +GCC's own private headers, and what you are compiling against is the compiler's internals as +they happened to be on the day of that release. There is no stability commitment, no +deprecation cycle, and no list of what is safe to call. `tree.h` changes between releases +because a GCC developer needed it to. + +The consequences are concrete and you have seen most of them already: + +- The plugin has to be built by the compiler that will load it. Not the same version, the + same build. {term("plugin ABI", "The check")} compares the configure line. +- No binary plugin can be distributed. Every plugin ships as source and is built on the + target machine, which is why every distribution has a separate `-plugin-dev` package. +- `-fno-rtti` is not a style choice. GCC is built without RTTI and a plugin that disagrees + gets link errors about typeinfo that name no cause. +- What arrives with an event is a `void *` whose real type is written in the call site and + nowhere else. Casting it wrong is a segfault, and `BP-PLUGIN` section 2 is a table of the + twenty nine call sites for exactly this reason. +- A pass you insert can be moved by a GCC release that reorders the pipeline, and your + `reference_pass_name` will still match, just somewhere else. + +The upgrade story is that there is no upgrade story. A plugin is pinned to a compiler, and +the honest way to maintain one is to test it against each release and fix what broke. + +Which is the whole argument for this repository's own design. `gxplug` emits JSON and the +Python side never parses a dump, so the fragile part is sixty lines of C++ in one file that +either compiles against a given GCC or does not, and the four hundred lines that read its +output do not care which compiler produced them. + +## Doing this yourself + +If you have a compiler with its plugin headers, from B01 or from a container, everything +above is four commands. + +```sh +make -C gxplug probe GCC=gcc-16 # what it found, and where +make -C gxplug GCC=gcc-16 # the plugin +make -C gxplug examples GCC=gcc-16 # the five in this lesson +make -C gxplug examples-check GCC=gcc-16 +``` + +The last one is the interesting one. It builds all five, loads each, and asserts that `hello` +and `countpass` leave the assembly identical, that `gate` does not, and that the two broken +ones are refused. The same target runs in CI on Debian, Fedora, Homebrew and a from-source +GCC on every push that touches the directory, which is what stops this lesson from describing +plugins that only work on the machine they were written on. + +The prerequisite is the plugin development package, which is a separate install almost +everywhere: + +| Channel | What to install | +|---|---| +| Debian | `gcc-16` and `gcc-16-plugin-dev` | +| Fedora | `gcc` and `gcc-plugin-devel` | +| Homebrew | `gcc`, which includes them | +| source | nothing, `make install` puts them under the prefix | + +Then point `-fplugin=` at a `.so` and give it a `./` if it is in the current directory. + +## Boss fight + +Eight questions about loading, events, passes and gates. + + python lessons/b05-the-plugin/grade.py + +Three of them are about failures rather than successes, which is where the time goes. The +last one has an answer that is a number, and the number is not the one most people say. + +## What to read next + +This is the end of M2. You can build GCC, bootstrap it, run it under a debugger, run its test +suite, and load code of your own into it. Everything from here on is about what the compiler +does rather than how to get at it. + +M3 is GIMPLE properly: the twenty odd statement codes, what a `GIMPLE_ASSIGN` really holds, +how the CFG is built and maintained, and the point in the pipeline where a function stops +being a tree and starts being a program. +""") + +raise SystemExit(lesson.save()) diff --git a/lessons/b05-the-plugin/grade.py b/lessons/b05-the-plugin/grade.py new file mode 100644 index 0000000..77c384d --- /dev/null +++ b/lessons/b05-the-plugin/grade.py @@ -0,0 +1,356 @@ +"""The B05 boss fight: eight questions about loading, events, passes and gates. + + python lessons/b05-the-plugin/grade.py + python lessons/b05-the-plugin/grade.py --says 'int plugin_is_GPL_compatible; 5; ...' + +Answers are separated by semicolons, matching B01 through B04. + +Three of the eight are computed rather than typed in: the number of fields the default +version check compares is counted out of the excerpt of `plugin.cc` the notebook printed, +and the two numbers at the end come out of the same recorded gxplug stream. So an answer +here cannot drift away from the compiler the lesson was written against. + +The three worth thinking about are the failures. Two of them are mistakes GCC tells you +about at once, in a way that names the cause, and one of them is a mistake GCC cannot tell +you about at all, which is why it is the one that costs an afternoon. + +Marking is lenient about spelling and strict about the answer. Whitespace, case, a trailing +period or semicolon, a leading `a `, `the ` or `-` and the order of a comma separated list +are all ignored. +""" + +from __future__ import annotations + +import argparse +import re +import sys +import textwrap +from dataclasses import dataclass, field +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT)) + +from gxray import plug, source # noqa: E402 + +#: Where the answer and the explanation sit, under the number the question was asked with. +INDENT = " " * 7 + + +def tidy(said: str) -> str: + """One answer, with everything that is not the answer taken off it.""" + out = re.sub(r"\s+", " ", said.strip().lower()).strip(" .!;") + for prefix in ("a ", "an ", "the ", "-", "it is ", "it ", "gcc "): + out = out.removeprefix(prefix) + return out.strip() + + +def listed(said: str) -> str: + """A comma separated answer, sorted, so the order it was typed in does not matter.""" + return ",".join(sorted(part for part in re.split(r"[,\s]+", tidy(said)) if part)) + + +def paragraph(text: str, indent: str = INDENT, first: str | None = None) -> str: + """One long line of prose, wrapped to fit a terminal and indented under its question.""" + return textwrap.fill( + " ".join(text.split()), + width=88, + initial_indent=indent if first is None else first, + subsequent_indent=indent, + ) + + +@dataclass(frozen=True) +class Question: + #: The question, as one unwrapped paragraph. Wrapping happens when it is printed. + ask: str + + #: The right answer, computed from the recording where it can be. + answer: str + + #: What the reader should take away when they got it wrong, unwrapped. + about: str + + #: How to compare. `list` ignores the order of a comma separated answer, `near` allows a + #: factor of two on a number, and the default is exact once both sides have been tidied. + compare: str = "exact" + + #: A nudge, printed only with --hints. + hint: str = "" + + #: Other wordings that are the same answer. + accepts: tuple[str, ...] = field(default_factory=tuple) + + def marks(self, said: str) -> bool: + if self.compare == "list": + return listed(said) == listed(self.answer) + got = tidy(said) + return got == tidy(self.answer) or got in tuple(tidy(a) for a in self.accepts) + + def first(self) -> str: + """The opening clause, for the one line summary at the end.""" + words = self.ask.split() + short = " ".join(words[:11]) + return short + (" ..." if len(words) > 11 else "") + + +def questions() -> list[Question]: + cuts = source.load_extract("b05") + fields = sum(1 for line in cuts["version"].lines if "strcmp (" in line) + + session = plug.load_session() + runs = session.stream.runs + marked = sum(1 for one in runs if one.changed) + + return [ + Question( + ask=( + "GCC looks one symbol up by name before it calls anything in your plugin, never" + " reads it, and refuses to compile at all without it. Name the symbol." + ), + answer="plugin_is_GPL_compatible", + accepts=( + "int plugin_is_gpl_compatible", + "int plugin_is_gpl_compatible;", + "plugin_is_gpl_compatible;", + ), + about=( + "`int plugin_is_GPL_compatible;`, at file scope. The check is a `dlsym` for that" + " name and nothing more, so the type does not matter and the value is never" + " looked at. What does matter is that it fails with `fatal_error` rather than a" + " warning, before your `plugin_init` runs, which is why a plugin missing it" + " prints nothing of its own before the compilation stops." + ), + hint="it is a definition, not a function, and its value is never used", + ), + Question( + ask=( + "How many fields does plugin_default_version_check compare before it agrees that" + " a plugin was built for this compiler? A number." + ), + answer=str(fields), + accepts=(f"{fields} fields", "five"), + about=( + f"{fields}. The version, the datestamp, the development phase, the revision, and" + " the entire configure command line. People expect the first and are surprised" + " by the last, which is the one that actually bites: it means a plugin is tied" + " to a build and not to a release." + ), + hint="more than one, and the last one is a whole command line", + ), + Question( + ask=( + "Two machines have GCC 16.2.0 from the same tarball. One was configured with" + " --enable-checking=release and the other without. A plugin built on the first" + " is refused by the second. Name the field that refused it." + ), + answer="configuration_arguments", + accepts=( + "configuration arguments", + "configure arguments", + "configure line", + "configuration_arguments", + "the configure command line", + "configure command line", + ), + about=( + "`configuration_arguments`, compared as a string. It is there because" + " `--enable-checking` changes struct layouts and `--enable-languages` changes" + " which trees exist, so two builds of one release genuinely have different" + " plugin ABIs. The string comparison is crude and it is the only thing between" + " you and a crash with no diagnostic. It is also why no plugin can be shipped as" + " a binary." + ), + hint="it is the fifth of the five, and it is not a version number", + ), + Question( + ask=( + "You register your pass with register_callback (name," + " PLUGIN_PASS_MANAGER_SETUP, my_setup_func, &pass_info), copying the shape of" + " every other registration you have written. What does GCC do?" + ), + answer="asserts", + accepts=( + "assert", + "asserts", + "aborts", + "crashes", + "internal compiler error", + "ice", + "gcc_assert", + "gcc_assert fires", + "assertion failure", + "trips an assertion", + ), + about=( + "It trips `gcc_assert (!callback)` and dies. PLUGIN_PASS_MANAGER_SETUP is a" + " pseudo-event: it is handled inside `register_callback` and never fired, so" + " there is nothing for a callback to be called from. The fourth argument is not" + " user data being kept for later, it is a `register_pass_info *` read on the" + " spot. Three of the twenty six names in plugin.def work this way, and none of" + " them looks any different from the ones that do not." + ), + hint="the third argument has nowhere to be called from", + ), + Question( + ask=( + "You want to switch off the pass whose dump you get with -fdump-tree-cddce1." + " What name do you compare current_pass->name against?" + ), + answer="cddce", + accepts=("cddce", '"cddce"'), + about=( + "`cddce`. The trailing digit in a dump name is the instance number the pass" + " manager assigned, not part of the pass name, because the same pass is in the" + " pipeline more than once. Comparing against `cddce1` matches nothing, and" + " nothing is exactly what it reports: no error, no warning, and a compilation" + " that looks like a successful experiment. This is the same distinction that" + " `ref_pass_instance_number` exists for, seen from the other side." + ), + hint="the pass is in the pipeline more than once", + ), + Question( + ask=( + "In a register_pass_info you set reference_pass_name to ssa and" + " ref_pass_instance_number to 0. What does the 0 mean? A few words." + ), + answer="every instance", + accepts=( + "every instance", + "all instances", + "every run of that pass", + "all of them", + "every occurrence", + "each instance", + ), + about=( + "Every instance of the reference pass, so your pass is inserted once for each" + " time `ssa` appears in the pipeline. 1 does not mean the first one it finds: it" + " means the instance marked `TODO_mark_first_instance`, which is a different" + " thing from `static_pass_number == 1`. Getting this wrong gives you a pass that" + " runs more often than you meant, which usually shows up as duplicated output" + " rather than as an error." + ), + hint="it is not a count and it is not an index", + ), + Question( + ask=( + "Your GIMPLE pass deletes a statement, returns 0 from execute, and works. Two" + " passes later the compilation dies inside code you did not write. What did you" + " leave out of the pass? Name the thing, not the value." + ), + answer="todo flags", + accepts=( + "todo flags", + "todo", + "todo_flags_finish", + "todo mask", + "return todo flags", + "a todo mask", + "the todo return value", + ), + about=( + "The TODO mask. What `execute` returns tells the pass manager what needs putting" + " back together: the CFG cleaned up, SSA renamed, the function verified." + " Returning 0 says nothing needs doing, so the pass manager believes the IR is" + " consistent when it is not, and the failure lands in whichever later pass first" + " trips over it. It is the most expensive mistake a first plugin can make" + " because the backtrace points at innocent code. A pass that only reads can" + " honestly return 0, which is why the one in this lesson does." + ), + hint="the pass manager is waiting to be told something, and 0 tells it nothing", + ), + Question( + ask=( + f"gxplug watched {len(runs)} pass runs on a nine line program. Roughly how many" + " of them changed the statement count, the insn count, the block count or the" + " property bits? A number." + ), + answer=str(marked), + compare="near", + about=( + f"{marked} of {len(runs)}, about {100 * marked // len(runs)}%. Read it carefully" + " in both directions. It is not that the other passes did nothing: a pass that" + " rewrites an expression in place changes none of those four counts and did real" + " work. What the number is good for is the honest version of the sentence people" + " say about GCC having hundreds of passes. On a program this small most of them" + " look at the function and leave, and the ones that move the counts are" + " concentrated around lowering, SSA construction and expand." + ), + hint="most people guess most of them", + ), + ] + + +def marks(question: Question, said: str) -> bool: + """One answer, marked. `near` is the numeric one, where a factor of two either way is fine.""" + if question.compare != "near": + return question.marks(said) + found = re.search(r"-?\d[\d,]*", said.replace(" ", "")) + if not found: + return False + got = int(found.group(0).replace(",", "")) + want = int(question.answer) + return want // 2 <= got <= want * 2 + + +def put(n: int, question: Question, hints: bool) -> str: + """Ask one question, and read the answer if there is somebody there to give one.""" + print() + print(paragraph(question.ask, indent=" ", first=f"{n}. ")) + if hints and question.hint: + print(f" ({question.hint})") + if not sys.stdin.isatty(): + return "" + return input(" > ").strip() + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + parser.add_argument("--says", help="the answers, semicolon separated, in the order asked") + parser.add_argument("--hints", action="store_true", help="print a nudge with each question") + args = parser.parse_args(sys.argv[1:] if argv is None else argv) + + asked = questions() + typed = [part.strip() for part in args.says.split(";")] if args.says else [] + + session = plug.load_session() + print(f"{len(session.invocations)} recorded compilations by {session.compiler}.") + print(f"{len(asked)} questions about writing the plugins that did it. Semicolons between.") + + answers = [] + for n, question in enumerate(asked, start=1): + if typed: + answers.append(typed[n - 1] if n <= len(typed) else "") + else: + answers.append(put(n, question, args.hints)) + + print() + scored = 0 + for question, said in zip(asked, answers, strict=True): + right = marks(question, said) + scored += right + print(f"{'right' if right else 'wrong':<7}{question.first()}") + print(f"{INDENT}{question.answer}") + if not right: + print(f"{INDENT}you said: {said or '(nothing)'}") + print(paragraph(question.about)) + + print(f"\n{scored} of {len(asked)}.") + if scored == len(asked): + print("You can write a plugin, say why one was refused, put a pass of your own in the") + print("pipeline at a place you chose, switch one of GCC's off, and explain why none of") + print("it will survive the next release without being rebuilt.") + print("That is M2. M3 next: GIMPLE, properly.") + return 0 + print("\nEverything except the reasoning questions is in the notebook. To reread:") + print(" from gxray import plug, source") + print(" session = plug.load_session()") + print(" print(plug.body('countpass'))") + print(" print(source.load_extract('b05')['version'].numbered())") + print(" print(source.load_extract('b05')['pseudo'].numbered())") + return 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/lessons/b05-the-plugin/record.py b/lessons/b05-the-plugin/record.py new file mode 100644 index 0000000..c9c09f0 --- /dev/null +++ b/lessons/b05-the-plugin/record.py @@ -0,0 +1,514 @@ +"""Build five plugins against a real GCC 16.2, load each one, and keep every byte. + + python lessons/b05-the-plugin/record.py + python lessons/b05-the-plugin/record.py --check + +A plugin cannot be built on Compiler Explorer and cannot be built in a notebook. It needs +GCC's private headers, a C++ compiler, and the same GCC that will load it, and a reader on +Colab has a shallow clone and a Python kernel. What they do have is the plugin sources, +because `gxplug/examples/` is in this repository, and this recording of what those sources +do when a compiler runs them. + +So the recorder is the Tier 1 half of B05 executed once, on a machine that has a real GCC, +with everything it printed written to `corpora/plug/b05.json`. Five plugins: + + hello the smallest thing that is still a plugin, one callback per function + countpass a pass of the reader's own, inserted after `ssa`, with its own dump file + gate the same mechanism used to switch one of GCC's passes off from outside + nolicence refused, because the GPL symbol is missing + wrongver refused, because it claims to have been built by a different compiler + +Plus the `gxplug` event stream itself, the pipeline with the new pass in it, and the +assembly on both sides of the gate, which is the one recording where the compiler's output +is meant to differ. + +Absolute paths are scrubbed on the way out. A corpus with somebody's home directory in it is +a corpus that says who recorded it, and every string here goes into a notebook a stranger +reads. + +`--check` re-asserts every fact the lesson states against what is already committed, without +building or compiling anything, which is what the test suite of this repository calls. +""" + +from __future__ import annotations + +import argparse +import json +import os +import re +import shutil +import subprocess +import sys +from datetime import date +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT)) + +from gxray import plug, source # noqa: E402 +from tools.refcheck import GCC_ROOT, PINNED_TAG # noqa: E402 + +OUT = ROOT / "corpora" / "plug" / "b05.json" +CUTS = ROOT / "corpora" / "source" / "b05.json" +GXPLUG = ROOT / "gxplug" +EXAMPLES = GXPLUG / "examples" + +#: The program every recording compiles. L2 because it is the one with a static function, a +#: struct and a loop, so `countpass` has phis to count and `ivopts` has work to skip. +PROGRAM = ROOT / "corpora" / "programs" / "l2.c" + +#: The other one, for the event stream. L1 is small enough that a reader can hold the whole +#: pass tape in their head, which is the point of showing the stream at all. +LADDER = ROOT / "corpora" / "programs" / "l1.c" + +#: The compiler. Overridable, but the recording committed here was made by the Homebrew +#: GCC 16.2.0, which is the same release as the pinned tree, so a line number cited from +#: `vendor/gcc` is a line number in the compiler that produced this output. +GCC = os.environ.get("GXPLUG_GCC", "gcc-16") + +#: The pass `gate` switches off. Chosen by trying the obvious candidates and keeping the one +#: whose absence is visible in the assembly of L2: induction variable optimization, which +#: rewrites the loop's addressing. `check()` below insists it still is. +GATED = "ivopts" + +#: And the one that changes nothing when you switch it off, which is the more instructive +#: half. Early inlining is not the inliner that matters here. +UNGATED = "einline" + +#: Where each span comes from. `first` and `last` are inclusive line numbers in the pinned +#: tree, and `cite` is asserted against them so a span that moves cannot quietly keep a +#: citation that now points at the wrong code. +SPANS: tuple[dict, ...] = ( + { + "name": "dlopen", + "path": "gcc/plugin.cc", + "first": 699, + "last": 708, + "about": "How a plugin is loaded, and the one flag that decides what a mismatch does", + "cite": "gcc/plugin.cc:699@releases/gcc-16.2.0", + }, + { + "name": "licence", + "path": "gcc/plugin.cc", + "first": 713, + "last": 717, + "about": "The licence check: a symbol looked up by name, and a fatal error if absent", + "cite": "gcc/plugin.cc:713@releases/gcc-16.2.0", + }, + { + "name": "initcall", + "path": "gcc/plugin.cc", + "first": 731, + "last": 740, + "about": "Calling plugin_init, and what a non-zero return does to the compilation", + "cite": "gcc/plugin.cc:731@releases/gcc-16.2.0", + }, + { + "name": "version", + "path": "gcc/plugin.cc", + "first": 1004, + "last": 1025, + "about": "The default version check, which compares five fields and not one", + "cite": "gcc/plugin.cc:1004@releases/gcc-16.2.0", + }, + { + "name": "defevent", + "path": "gcc/plugin.def", + "first": 20, + "last": 39, + "about": "The event list is a file of one-line macro calls, and the order is the ABI", + "cite": "gcc/plugin.def:20@releases/gcc-16.2.0", + }, + { + "name": "pseudo", + "path": "gcc/plugin.cc", + "first": 450, + "last": 469, + "about": "register_callback, where three of the events are handled and never fired", + "cite": "gcc/plugin.cc:450@releases/gcc-16.2.0", + }, + { + "name": "dispatch", + "path": "gcc/plugin.cc", + "first": 582, + "last": 592, + "about": "What firing an event actually is: a linked list walked in registration order", + "cite": "gcc/plugin.cc:582@releases/gcc-16.2.0", + }, + { + "name": "info", + "path": "gcc/tree-pass.h", + "first": 328, + "last": 344, + "about": "The four fields that decide where a plugin's pass goes", + "cite": "gcc/tree-pass.h:328@releases/gcc-16.2.0", + }, + { + "name": "position", + "path": "gcc/passes.cc", + "first": 1377, + "last": 1401, + "about": "The match: same pass type, same name, and the right instance", + "cite": "gcc/passes.cc:1377@releases/gcc-16.2.0", + }, + { + "name": "gatecall", + "path": "gcc/passes.cc", + "first": 2597, + "last": 2608, + "about": "The gate, and the one event that is handed a pointer to write through", + "cite": "gcc/passes.cc:2597@releases/gcc-16.2.0", + }, +) + + +def scrub(text: str) -> str: + """Take the recording machine out of the recording. + + Three things leak: the repository's own path, the Homebrew prefix in the include flags, + and the temporary directory the compilations run in. All three are replaced with + something a reader can recognize, and none of them carries information the lesson needs. + """ + text = text.replace(str(ROOT) + "/", "").replace(str(ROOT), ".") + text = re.sub(r"/opt/homebrew|/home/linuxbrew/\.linuxbrew", "$(brew --prefix)", text) + text = re.sub(r"/(?:var/folders|tmp)/[^\s'\"]*", "/tmp/b05", text) + return text + + +def run(argv: list[str], cwd: Path | None = None) -> subprocess.CompletedProcess: + return subprocess.run( + argv, cwd=cwd or ROOT, capture_output=True, text=True, timeout=300, check=False + ) + + +def compile_one(name: str, about: str, args: list[str], keep_asm: bool = True) -> dict: + """One compilation, recorded whole. `args` is everything after the compiler's name.""" + out = ROOT / "b05-scratch.s" + argv = [GCC, *args, "-o", str(out)] + done = run(argv) + asm = None + if keep_asm and out.is_file(): + asm = out.read_text(encoding="utf-8") + out.unlink(missing_ok=True) + return { + "about": scrub(about), + "argv": [scrub(a) for a in argv], + "returncode": done.returncode, + "stdout": scrub(done.stdout), + "stderr": scrub(done.stderr), + **({"asm": scrub(asm)} if asm is not None else {}), + } + + +def build() -> str: + """Build the plugin and the five examples, and return what the probe decided. + + A failure here is not something to work around. It means the reader following the same + four commands in the lesson would fail too, and the lesson would be wrong. + """ + probe = run(["make", "-C", str(GXPLUG), "probe", f"GCC={GCC}"]) + if probe.returncode != 0: + raise SystemExit(f"make probe failed:\n{probe.stdout}{probe.stderr}") + for target in ("all", "examples"): + made = run(["make", "-C", str(GXPLUG), target, f"GCC={GCC}"]) + if made.returncode != 0: + raise SystemExit(f"make {target} failed:\n{made.stdout}{made.stderr}") + return scrub(probe.stdout) + + +def pipeline() -> dict: + """`-fdump-passes` with the reader's pass in it, and where it landed. + + The list is four hundred lines long and the lesson wants six of them. The six are picked + by finding the new pass rather than by line number, so this keeps working when GCC adds + a pass above it. + """ + done = run( + [ + GCC, + "-O2", + "-S", + f"-fplugin={EXAMPLES / 'countpass.so'}", + "-fdump-passes", + str(PROGRAM), + "-o", + os.devnull, + ] + ) + lines = [line.rstrip() for line in done.stderr.splitlines() if ":" in line] + where = next((i for i, line in enumerate(lines) if "gxcount" in line), -1) + if where < 0: + raise SystemExit(f"gxcount is not in -fdump-passes:\n{done.stderr[:2000]}") + return { + "total": len(lines), + "index": where, + "around": lines[max(0, where - 3) : where + 4], + } + + +def stream() -> str: + """The gxplug event stream for L1 at -O2, as newline delimited JSON.""" + events = ROOT / "b05-events.ndjson" + done = run( + [ + GCC, + "-O2", + "-S", + f"-fplugin={GXPLUG / 'gxplug.so'}", + f"-fplugin-arg-gxplug-out={events}", + str(LADDER), + "-o", + os.devnull, + ] + ) + if done.returncode != 0 or not events.is_file(): + raise SystemExit(f"gxplug produced nothing:\n{done.stderr}") + text = events.read_text(encoding="utf-8") + events.unlink() + return text + + +def record() -> dict: + probe = build() + program, plain = str(PROGRAM), ["-O2", "-S", str(PROGRAM)] + runs = { + "plain": compile_one("plain", f"{program}, no plugin at all", plain), + "hello": compile_one( + "hello", + "the smallest plugin, one callback per function and one at the end", + ["-O2", "-S", f"-fplugin={EXAMPLES / 'hello.so'}", program], + ), + "hello-arg": compile_one( + "hello-arg", + "the same plugin, given an argument", + [ + "-O2", + "-S", + f"-fplugin={EXAMPLES / 'hello.so'}", + "-fplugin-arg-hello-who=reader", + program, + ], + keep_asm=False, + ), + "countpass": compile_one( + "countpass", + "a pass of your own, running after ssa on every function", + ["-O2", "-S", f"-fplugin={EXAMPLES / 'countpass.so'}", program], + ), + "gated": compile_one( + "gated", + f"{GATED} switched off from outside, and the assembly moves", + [ + "-O2", + "-S", + f"-fplugin={EXAMPLES / 'gate.so'}", + f"-fplugin-arg-gate-off={GATED}", + program, + ], + ), + "ungated": compile_one( + "ungated", + f"{UNGATED} switched off the same way, and nothing moves", + [ + "-O2", + "-S", + f"-fplugin={EXAMPLES / 'gate.so'}", + f"-fplugin-arg-gate-off={UNGATED}", + program, + ], + ), + "gate-dumpname": compile_one( + "gate-dumpname", + "the same request with a dump name instead of a pass name, which matches nothing", + [ + "-O2", + "-S", + f"-fplugin={EXAMPLES / 'gate.so'}", + "-fplugin-arg-gate-off=cddce1", + program, + ], + keep_asm=False, + ), + "nolicence": compile_one( + "nolicence", + "refused before plugin_init runs, for a symbol that is not there", + ["-O2", "-S", f"-fplugin={EXAMPLES / 'nolicence.so'}", program], + keep_asm=False, + ), + "wrongver": compile_one( + "wrongver", + "refused by its own version check, which is the plugin's job and not GCC's", + ["-O2", "-S", f"-fplugin={EXAMPLES / 'wrongver.so'}", program], + keep_asm=False, + ), + "missing": compile_one( + "missing", + "a plugin that is not there, which is a dlopen error and not a driver error", + ["-O2", "-S", "-fplugin=./no-such-plugin.so", program], + keep_asm=False, + ), + "badarg": compile_one( + "badarg", + "a mistyped -fplugin-arg, which gxplug refuses rather than ignoring", + [ + "-O2", + "-S", + f"-fplugin={GXPLUG / 'gxplug.so'}", + "-fplugin-arg-gxplug-bogus=1", + program, + ], + keep_asm=False, + ), + } + version = run([GCC, "--version"]).stdout.splitlines()[0] + return { + "recorded": date.today().isoformat(), + "tag": PINNED_TAG, + "compiler": version, + "target": run([GCC, "-dumpmachine"]).stdout.strip(), + "probe": probe, + "pipeline": pipeline(), + "stream": stream(), + "runs": runs, + } + + +def spans() -> dict: + for spec in SPANS: + want = f"{spec['path']}:{spec['first']}@{PINNED_TAG}" + if spec["cite"] != want: + print(f"{spec['name']}: cite says {spec['cite']}, the span starts at {want}") + raise SystemExit(1) + wanted = [{k: v for k, v in spec.items() if k != "cite"} for spec in SPANS] + return source.extract(GCC_ROOT, wanted, PINNED_TAG) + + +def check(session: plug.Session) -> list[str]: + """Every behavioural fact the notebook states, asserted here instead of in prose. + + A recording is dated, not verified. If GCC 17 renames a pass, moves the licence check or + stops firing an event, the paragraph next door is wrong and only this function can say + so. + """ + wrong: list[str] = [] + + def want(condition: bool, saying: str) -> None: + if not condition: + wrong.append(saying) + + plain, hello = session["plain"], session["hello"] + want(not plain.refused, "the program does not compile without any plugin at all") + want(not hello.refused, f"hello was refused: {hello.said}") + greeting = "hello, world: 2 function(s) in this unit" + want( + hello.said == ["hello: dist2", "hello: nearest", greeting], + f"hello now says {hello.said}", + ) + want(hello.asm == plain.asm, "hello changed the generated code, which no observer may do") + want( + "reader" in session["hello-arg"].said[-1], + "the -fplugin-arg- value no longer reaches the plugin", + ) + + counted = session["countpass"] + want(not counted.refused, f"countpass was refused: {counted.said}") + want(counted.asm == plain.asm, "a pass that only reads changed the generated code") + want( + [line.split(":")[0] for line in counted.said] == ["gxcount", "gxcount"], + f"countpass ran on {len(counted.said)} functions, not 2", + ) + want( + "6 phi(s)" in counted.said[1], + f"nearest no longer has six phis after ssa: {counted.said[1]}", + ) + + where = session.pipeline + want( + any("gxcount" in line for line in where["around"]), + "the new pass is no longer in -fdump-passes", + ) + around = where["around"] + after = around[around.index(next(line for line in around if "gxcount" in line)) - 1] + want("ssa" in after, f"the pass before gxcount is now {after.strip()!r}, not tree-ssa") + want(where["total"] > 300, f"-fdump-passes prints {where['total']} passes, which is too few") + + gated, ungated = session["gated"], session["ungated"] + want(not gated.refused, f"switching {GATED} off failed the compilation: {gated.said}") + want(gated.asm != plain.asm, f"switching {GATED} off no longer changes the assembly") + want(f"refused {GATED} 1 time" in gated.said[-1], f"the gate report is now {gated.said[-1]!r}") + want(ungated.asm == plain.asm, f"switching {UNGATED} off now changes the assembly") + want( + f"refused {UNGATED} 2 time" in ungated.said[-1], + f"{UNGATED} is no longer gated on twice: {ungated.said[-1]!r}", + ) + want( + "refused cddce1 0 time" in session["gate-dumpname"].said[-1], + "a dump name now matches a pass name, which would make the section pointless", + ) + + for name, saying in ( + ("nolicence", "not licensed under a GPL-compatible license"), + ("wrongver", "built against GCC 15.1.0, loaded into GCC 16.2.0"), + ("missing", "cannot load plugin"), + ("badarg", "unknown argument"), + ): + one = session[name] + want(one.refused, f"{name} compiled successfully, which defeats the point of it") + want( + any(saying in line for line in one.said), + f"{name} no longer says {saying!r}, it says {one.said[:2]}", + ) + want( + not any("this line never prints" in line for line in session["nolicence"].said), + "the plugin without a licence symbol got as far as running its own code", + ) + + events = session.stream + runs = events.runs + want(len(runs) > 100, f"the stream has {len(runs)} pass runs in it, which is too few") + want( + events.functions == ["_f"] or events.functions == ["f"], + f"the stream is of {events.functions}, and L1 has one function called f", + ) + marked = [r for r in runs if r.changed] + want( + 0 < len(marked) < len(runs) // 2, + f"{len(marked)} of {len(runs)} runs left a mark, and the lesson says most do not", + ) + want( + any(r.name == "expand" for r in runs), + "expand is not in the stream, so the GIMPLE to RTL step cannot be shown", + ) + return wrong + + +def main(argv: list[str] | None = None) -> int: + ap = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + ap.add_argument("--check", action="store_true", help="assert against what is already there") + args = ap.parse_args(sys.argv[1:] if argv is None else argv) + + if not args.check: + if shutil.which(GCC) is None: + print(f"no {GCC} on PATH. Set GXPLUG_GCC, or read B01 and build one.") + return 1 + OUT.parent.mkdir(parents=True, exist_ok=True) + OUT.write_text(json.dumps(record(), indent=1) + "\n", encoding="utf-8") + CUTS.parent.mkdir(parents=True, exist_ok=True) + CUTS.write_text(json.dumps(spans(), indent=1) + "\n", encoding="utf-8") + + session = plug.load_session() + shown = source.load_extract("b05") + wrong = check(session) + for line in wrong: + print(f" {line}") + verb = "checked" if args.check else "wrote" + print( + f"{verb} {OUT.relative_to(ROOT)}, {len(session.invocations)} compilations by " + f"{session.compiler}, {len(session.stream.runs)} pass runs, recorded {session.recorded}" + ) + print(f"{verb} {CUTS.relative_to(ROOT)}, {len(shown)} spans, {shown.lines()} lines") + return 1 if wrong else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_plug.py b/tests/test_plug.py index 5913f93..5e043df 100644 --- a/tests/test_plug.py +++ b/tests/test_plug.py @@ -392,3 +392,169 @@ def test_the_makefile_check_target_passes(built_plugin, gcc_plugin): ) assert result.returncode == 0, result.stdout + result.stderr assert "assembly identical" in result.stdout + + +# --- the examples, and the recording of them ------------------------------------------ + +EXAMPLES = GXPLUG / "examples" +SESSION = plug.load_session() + + +def test_the_five_examples_are_all_there(): + """B05 names five plugins and the Makefile builds whatever is in this directory. + + A source file added here without a section in the lesson is a plugin nobody reads, and + one removed is a lesson that talks about a file that is gone. + """ + found = sorted(path.stem for path in EXAMPLES.glob("*.cc")) + assert found == ["countpass", "gate", "hello", "nolicence", "wrongver"] + + +@pytest.mark.parametrize("name", ["hello", "countpass", "gate", "wrongver"]) +def test_every_example_that_is_meant_to_load_says_it_is_GPL_compatible(name): + """Four of the five carry the symbol. The fifth is the lesson about the symbol.""" + text = (EXAMPLES / f"{name}.cc").read_text(encoding="utf-8") + assert "int plugin_is_GPL_compatible;" in text + + +def test_the_refused_example_really_is_missing_the_symbol(): + text = (EXAMPLES / "nolicence.cc").read_text(encoding="utf-8") + # The symbol appears once, inside the comment that says it is the missing line. + assert text.count("int plugin_is_GPL_compatible;") == 1 + assert "int plugin_is_GPL_compatible; */" in text + + +def test_the_recorded_session_is_the_one_the_lesson_describes(): + assert SESSION.compiler.startswith("gcc-16") + assert "16.2.0" in SESSION.compiler + assert len(SESSION.invocations) == 11 + assert SESSION.stream.runs + + +def test_a_recording_that_is_not_there_says_which_ones_are(): + with pytest.raises(plug.PlugError, match="no recording called 'nope'"): + SESSION["nope"] + + +def test_a_missing_corpus_names_the_recorder(tmp_path): + with pytest.raises(plug.PlugError, match="record.py"): + plug.load_session(root=tmp_path) + + +def test_the_passive_examples_left_the_assembly_alone(): + """The same promise gxplug makes, checked for the two examples that also make it.""" + plain = SESSION["plain"].asm + assert plain + for name in ("hello", "countpass"): + assert SESSION[name].asm == plain, name + + +def test_the_gate_example_did_not(): + """And the one that is not passive, because a demonstration that changes nothing is not one.""" + assert SESSION["gated"].asm != SESSION["plain"].asm + assert len(SESSION.diff("plain", "gated")) > 10 + + +def test_diffing_a_recording_with_no_assembly_says_so(): + with pytest.raises(plug.PlugError, match="did not keep its assembly"): + SESSION.diff("plain", "badarg") + + +@pytest.mark.parametrize("name", ["nolicence", "wrongver", "missing", "badarg"]) +def test_every_refusal_is_a_failed_compilation_and_not_a_warning(name): + """A plugin that cannot load stops the build. Nothing here is skipped with a warning.""" + one = SESSION[name] + assert one.refused + assert one.said + + +def test_the_recorded_corpus_carries_nobody_s_home_directory(): + """Scrubbing, checked. A corpus is read by strangers in a notebook.""" + text = (REPO / "corpora" / "plug" / "b05.json").read_text(encoding="utf-8") + assert "/Users/" not in text + assert "/home/" not in text + assert "/opt/homebrew" not in text + + +@needs_plugin_gcc +def test_the_examples_do_what_the_lesson_says_they_do(gcc_plugin): + """The Makefile target, from the entry point a reader would type. + + Five plugins built and loaded: two that change nothing, one that changes the assembly + on purpose, and two that are refused. If this fails, B05 is describing plugins that no + longer work on this compiler. + """ + result = subprocess.run( + ["make", "-C", str(GXPLUG), "examples-check", f"GCC={gcc_plugin}"], + capture_output=True, + text=True, + ) + assert result.returncode == 0, result.stdout + result.stderr + assert "gate switched a pass off and the assembly moved" in result.stdout + + +# --------------------------------------------------------------------------- +# The boss fight. +# +# Three of B05's eight answers are computed out of the corpus rather than written down, so +# they cannot drift away from the compiler the lesson was recorded against. What can drift +# is the marking: an `accepts` list that no longer covers the phrasing the notebook uses is +# a question nobody can get right, and there is no other check for that. + +GRADE = REPO / "lessons" / "b05-the-plugin" / "grade.py" + + +def _grader(): + """B05's grade.py, loaded by path. + + Every lesson has a module called `grade`, so importing by name would hand back whichever + one got there first. + """ + import importlib.util + import sys + + spec = importlib.util.spec_from_file_location("grade_b05", GRADE) + module = importlib.util.module_from_spec(spec) + # Registered before it is executed, not after. @dataclass looks its own class up in + # sys.modules while the module body is still running, and on 3.14 a module that is not + # there yet fails with an AttributeError about NoneType that names no cause. + sys.modules["grade_b05"] = module + spec.loader.exec_module(module) + return module + + +def test_the_grader_computes_its_numbers_out_of_the_corpus(): + """Not written down. Five version fields and the changed-run count, both derived.""" + asked = _grader().questions() + assert len(asked) == 8 + assert asked[1].answer == "5" + + runs = SESSION.stream.runs + assert asked[7].answer == str(sum(1 for one in runs if one.changed)) + + +def test_the_grader_marks_the_answers_the_lesson_leads_you_to(): + module = _grader() + said = [ + "plugin_is_GPL_compatible", + "5", + "configuration_arguments", + "gcc_assert", + "cddce", + "every instance", + "todo flags", + module.questions()[7].answer, + ] + for question, answer in zip(module.questions(), said, strict=True): + assert module.marks(question, answer), question.ask + + +def test_the_grader_is_lenient_about_wording_and_strict_about_the_answer(): + module = _grader() + asked = module.questions() + assert module.marks(asked[0], " The plugin_is_GPL_compatible; ") + assert module.marks(asked[2], "the configure command line") + assert module.marks(asked[4], '"cddce"') + assert not module.marks(asked[4], "cddce1") + assert not module.marks(asked[5], "the first instance") + assert not module.marks(asked[7], "247") diff --git a/tools/tier0/experiments.toml b/tools/tier0/experiments.toml index 3b31811..d2bf58d 100644 --- a/tools/tier0/experiments.toml +++ b/tools/tier0/experiments.toml @@ -385,3 +385,20 @@ three of them fail to compile on purpose. So the check that keeps them honest is recorder's own, which asserts every behavioural fact the notebook states, and it runs against \ the recording in the test suite of this repository without a network.\ """ + +[[experiment]] +id = "b05-plugin" +kind = "offline" +question = "What can you do from inside the compiler without patching it, and what stops you?" +lessons = ["b05-the-plugin"] +why = """ +Offline because the subject cannot be built anywhere this checker can reach. A plugin is C++ \ +compiled against GCC's private headers and loaded into the same compiler that built it, and \ +Compiler Explorer will not build one, and a Colab kernel has neither the headers nor a \ +matching compiler. So corpora/plug/b05.json is eleven real compilations by a real GCC 16.2.0, \ +with the five plugin sources they loaded committed under gxplug/examples where a reader can \ +read every line of them. What keeps it honest is that the same five plugins are built and \ +loaded on four different compilers by the plugin workflow on every push that touches them, \ +and lessons/b05-the-plugin/record.py re-asserts every fact the notebook states against the \ +recording without compiling anything.\ +"""