Skip to content

Cogen improve comments review - #2767

Open
willend wants to merge 9 commits into
mainfrom
cogen-improve-comments-review
Open

willend wants to merge 9 commits into
mainfrom
cogen-improve-comments-review

Conversation

@willend

@willend willend commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Free-form text area

Please describe what your PR is adding in terms of features or bugfixes:

Code generator review: comments, bug fixes, dead code

Commits on cogen-improve-comments-review:

  1. b478b4e adds explanatory comments to instrument.l, instrument.y and cogen.c.in. There are no code changes.
  2. b546497 fixes the bugs found during that review and removes dead code.
  3. eb52255 adds grammar unit tests for McStas and McXtrace (examples/Tests_grammar/).
  4. aa71960 keeps expressions in {...} vector parameters and adds guards to the FUNNEL code.
  5. 4eab34a updates the ADR.

The reasoning is in ADR_20261007_GRAMMAR_FIXES.

⚠️ Changes simulation results

  • Diaphragm and Place were no-ops. DEFINE COMPONENT X INHERIT Y with no sections of its own inherited no code from Y. Diaphragm let the full beam through (about 16× the intensity behind the equivalent Slit) in both McStas and McXtrace. It now behaves like Slit.
  • ILL_H5 and ILL_H5_new: wrong reflectivity parameters in the V-cavity polarisers.
    • Cause: vector parameters written with expressions, such as rPar={1, 3.2*0.0219, 4.07, 1, 0.003}, were parsed as plain numbers, giving {1, 3.2, 0, 0.0219, 4.07, 1, 0.003}: 7 elements, shifted and wrong.
    • Effect of the fix: IN15_Vpolariser and WASP_Vpolariser now get the intended values. Intensity downstream of them rises by about 3 to 12 times; for example, H511_IN15_Detector goes from 0 to 1.6e4.
    • Unchanged: H5_I and the other detectors. The ILL %Example values may need updating.

Fixes

  • JUMP PREVIOUS no longer crashes the generator, and JUMP NEXT no longer goes to the first component. Bad JUMP targets are now an error.
  • A misspelled component name, or COPY of an undefined instance, now gives an error instead of a segfault.
  • MYSELF in a component's own parameters is now an error, instead of silently referring to the previous component.
  • EXTEND after component USERVARS is no longer dropped.
  • All declarations on a line are now found (double a; double b;).
  • %include "x.hpp" now works inside C blocks.
  • USERVARS read by Monitor_nD are now converted to double properly.
  • {...} vector parameters: each element is written into the generated code as the expression it is, so instrument parameters and function calls work. Literals are copied exactly as written, instead of being rounded to 6 digits.
  • FUNNEL: the particle-buffer allocation is now checked, and the batch size is reset at the start of each batch.
  • Fixed a use-after-free and several buffer overflows in the lexer and parser.

Cleanup

  • Removed the unused Pool API, parser wrappers and the unused vector parser in pygen.c.in.
  • Merged four copies of the particle-state table into one.

Testing

  • Generated code: regenerated all 332 McStas and 112 McXtrace examples against the old generator.
    • The only changes in value are in the eight instruments with {...} vectors. Six of them differ only in formatting (e.g. 1.0 vs 1); ILL_H5 and ILL_H5_new are described above.
    • Everything else is cosmetic.
  • Results: JUMP/SPLIT/GROUP, USERVARS, StatisticalChopper, Union and xraylib test instruments give identical results.
  • Grammar tests: Unittest_JUMP_RELATIVE, Unittest_INHERIT_DIAPHRAGM and Unittest_USERVARS_TYPES pass on both flavours. The six Unittest_GRAMMAR_ERRORS/*.instr.fail cases fail with clear errors.
  • Platforms: macOS arm64. Built with devel/bin/mccode-build-conda in the conda-forge mccode-dev env (flex/bison from conda-forge; macOS's bison 2.3 is too old). No new dependencies.

Follow-up

  • CHANGES_McStas and CHANGES_McXtrace should note the Diaphragm/Place behaviour change.

Declaration of use of AI-tools

  • Please add a checkmark here if you used AI-tools during the work for this contribution
  • Furter, please describe how / where and for what the tools were used:

^ was 🤖 Generated with Claude Code and using /ponytail full


Development OS / boundary conditions

Please describe what OS you developed and tested your additions on, and if any special dependencies are required:


PR Checklist for contributing to McStas/McXtrace

For a coherent and useful contribution to McStas/McXtrace, please fill in relevant parts of the checklist:

  • My work touches the code-generator in mccode/src

    • I have added reasoning and documentation for the change through an ADR record in our GRAMMAR section
    • I am attaching test output in the comments
  • My contribution contains something else

    • Explanation is added in free form text above or below the checklist

willend and others added 2 commits October 7, 2026 11:08
…ity.

I added explanatory comments to instrument.l, instrument.y and cogen.c.in, about 300 lines across the three. The 26 removed lines are old comments I reworded; no code changed.

Checking that nothing else changed: I built with devel/bin/mccode-build-conda in the mccode-dev env. The commented generator produces exactly the same C as the original for all 332 McStas example instruments, apart from the timestamp and output filename. Two side effects:

The script also ran cmake --install, so mccode-dev now has a McStas built from this working tree. That differs from what was there only by these comments.
I only built McStas, not McXtrace, and only on macOS.

What the comments cover:

Overview: how the three files fit together, and how the lexer's first-token trick lets one grammar handle both .instr and .comp files.
Component loading: how .comp files are read by a nested parse started from inside a grammar rule.
Lexer: its states and the stack it uses for %include files.
Parser: how INHERIT/EXTEND sections are joined, the two-step component rule (including the $N numbering), and why expressions are just token strings.
Generator: how the output maps onto structs, class functions and _<name>_var, plus a sketch of the raytrace() state machine.
Fixes to existing comments: a few were wrong, for example the DEFINITION_TYPE options, the def_uservars header, and ".com" instead of ".comp".
Behaviour changes (may change simulation results):
- Whole-component INHERIT ("DEFINE COMPONENT X INHERIT Y") now takes
  the code sections X leaves empty from Y. Before, nothing was
  inherited, so Diaphragm (INHERIT Slit) and Place (INHERIT Arm) were
  no-ops: Diaphragm let the full beam through (McStas and McXtrace).
  Diaphragm now gives the same results as Slit.
  SHARE is now emitted once per code block, so a class and the classes
  inheriting it no longer duplicate it. StatisticalChopper_Monitor now
  also compiles without a Monitor_nD elsewhere in the instrument.
- Literal vector parameters ({...}) are now written with full double
  precision (shortest of %.15g/%.17g that reads back exactly), instead
  of %g, which rounded to 6 significant digits. No shipped instrument
  is affected.
- particle_getvar()/particle_getuservar_byid() now convert numeric
  USERVARS to double instead of reading their memory as a double.
  Arrays, pointers and structs report failure. Monitor_nD user1..user9
  variables must still be declared double.

Bug fixes:
- JUMP PREVIOUS / NEXT / NEXT(n): targets are now resolved relative to
  the jumping component. Before, JUMP PREVIOUS crashed the generator
  and JUMP NEXT went to the first component. Unknown or out-of-range
  targets are now an error (before: silently jumped to MYSELF).
- Unknown component class, or COPY of an undefined instance: proper
  error instead of a segfault.
- MYSELF in a component's own parameters is now an error. Before, it
  silently referred to the previous component.
- PREVIOUS in an expression with no previous component no longer leaves
  an uninitialised value.
- Component USERVARS followed by EXTEND no longer drops the EXTEND block.
- DECLARE/USERVARS scanning finds every declaration on a line
  ("double a; double b;"), not just the first.
- %include inside C blocks accepts multi-character extensions (.hpp).
- Lexer: included file names are copied (use-after-free); a repeated
  %include of an embedded library no longer counts its line twice;
  indentation before %include is no longer echoed to stdout.
- Buffer overflows fixed: SEARCH SHELL paths, the DEPENDENCY/CFLAGS
  buffer (new dependency_add()), literal vectors > 100 elements, the
  system directory copy.
- Fixed "$s" format in the instrument parameter type error message.
- Generated code: #undef cone/polygon/polyhedron after DISPLAY, removed
  a stray "// HEST!" line, _index comment shows the real index.
- Removed the duplicate "Dependency: mcstas-r.o" message.

Cleanup:
- Removed the unused Pool API (memory.c, mccode.h.in, mcformat.c.in),
  parser_pool, and the mc_yyparse/mc_yyparse_component wrappers.
- One file-scope particle state table instead of four copies.
- Removed a no-op strcmp(dependency, "-lm") and unused variables.

Tested by regenerating all 332 McStas and 112 McXtrace examples against
the previous generator: no substantive output changes. Diffs were
limited to cosmetic lines and the getter casts. JUMP/SPLIT/GROUP,
user-var, StatisticalChopper, PowderN/fluorescence (xraylib) and Union
test instruments give identical results old vs new.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@willend

willend commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor Author

Test results

All comparisons are between the generator built from the parent commit (c2354743a plus comments only) and this branch. Built with devel/bin/mccode-build-conda in a conda-forge env on macOS arm64.

1. Regenerating all examples

Flavour Instruments Exit status differs Generated C differs in substance
McStas 332 0 0
McXtrace 112 0 0

Ignored when comparing: the date and file-name header lines, the version string, the _index comment, the removed // HEST! line, the new #undef cone/polygon/polyhedron, and the getter casts rval=(double)(p->var).

2. Results, old vs new generator (same seed)

Instrument Flavour Detectors Result
Unittest_JUMP_ITERATE McStas 1 identical
Unittest_JUMP_WHEN McStas 1 identical
Test_Jump_Iterate McStas 5 identical
Unittest_SPLIT McStas 1 identical
Test_GROUP McStas 1 identical
Test_GROUP_restore McStas 8 identical
Test_Monitor_nD_10_uservars McStas 1 identical
Test_StatisticalChopper McStas 5 identical
Test_GROUP McXtrace 1 identical
Test_Monitor_nD_10_uservars McXtrace 1 identical
Test_Mirrors McXtrace 5 identical
Test_PowderN (xraylib) McXtrace 2 identical
Test_FluoPowder (xraylib) McXtrace 10 identical (2 runs each)
Test_Fluorescence (xraylib) McXtrace 7 identical
Test_KN_Comp_Rayl_union (xraylib) McXtrace 3 identical
PowderCompton_union (xraylib) McXtrace 2 identical

The McStas runs used the build after the first round of fixes. The later changes were checked on McStas by regenerating every example (section 1), not by re-running these instruments. The McXtrace runs used the final build.

3. Bugs reproduced before and after

Case Before After
JUMP PREVIOUS (from c in a,b,c,d) generator segfault jumps to b
JUMP NEXT (from c) jumps to a jumps to d
JUMP NEXT(2) (from c) / unknown target jumps to MYSELF error
Misspelled component class error, then segfault error
COPY(PREVIOUS) as first component segfault error
int *x instrument parameter garbled message Illegal type int* ... x
MYSELF in component parameters referred to previous component error
MYSELF in WHEN works works
DECLARE double a; double b; int c; only a found a, b, c
%include "hdr.hpp" in C block treated as library hdr.hpp.h included
int / float USERVAR via particle_getvar() memory read as double converted to double
StatisticalChopper_Monitor without a Monitor_nD does not compile compiles and runs

4. Diaphragm (INHERIT Slit): intended behaviour change

Source → 1×1 cm aperture → PSD monitor (McStas: 1e5 rays, seed 1; McXtrace same setup).

Flavour Slit Diaphragm (old) Diaphragm (new)
McStas 0.00246395 0.0401748 0.00246395
McXtrace 7.80158e-06 0.000127205 7.80158e-06

5. Vector-literal precision (%g → exact)

Test_Pol_Mirror with a modified rUpPar (1e6 rays, seed 11). Both monitors are compared; the first, 9.11031e-05, is identical in every case.

rUpPar Written by old generator Old I New I
as shipped {1.0, 0.0219, 6.07, 2.0, 0.003} identical 21.2727 21.2727
Qc = 0.02191234 0.0219123 21.2869 21.2869
m = 2.0000049 2 21.2727 21.2728
all five values to 8 digits 6 digits 23.3289 23.3289

Shipped literals are reproduced exactly; worst case seen is 5e-6 relative.

Not tested

  • Linux (gcc, clang)
  • Windows (MSVC)
  • macOS x86_64
  • OpenACC/GPU, -DFUNNEL and MPI runs

@willend
willend requested review from farhi and g5t October 7, 2026 10:17
@g5t

g5t commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Would it be too much to ask for tests that exercise the changed behavior and bug fixes?

It'd be great if I could run mcstas-antlr pr_2767_tests.instr (or whatever) and know what I need to fix 😆

@willend
willend requested a review from mads-bertelsen October 7, 2026 10:37
McStas and McXtrace versions of tests for ADR_20261007_GRAMMAR_FIXES:
- Unittest_JUMP_RELATIVE: JUMP PREVIOUS(2) ITERATE and JUMP NEXT(2)
- Unittest_INHERIT_DIAPHRAGM: whole-component INHERIT (Diaphragm vs Slit)
- Unittest_USERVARS_TYPES: int USERVAR read by Monitor_nD, several
  declarations per line, component USERVARS+EXTEND, %include "x.hpp"
- Unittest_GRAMMAR_ERRORS: *.instr.fail cases that must fail code
  generation (MYSELF in parameters, bad JUMP targets, COPY of undefined
  instance, unknown component, illegal pointer type), with a README.

Expected values are in %Example lines. The old generator segfaults on or
mis-handles each of them; see the README for the observed old/new
behaviour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@willend

willend commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

@g5t and @mads-bertelsen new test instruments for you to look at on the McStas side will go in mcstas-comps/examples/Tests_grammar/

@farhi new test instruments for you to look at on the McXtrace side will go in mcxtrace-comps/examples/Tests_grammar

@willend

willend commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

Added eb5225511: grammar unit tests for the fixes in this PR (McStas and McXtrace, examples/Tests_grammar/). They may also be useful as reference cases for mccode-antlr.

  • Unittest_JUMP_RELATIVE: JUMP PREVIOUS(2) ITERATE and JUMP NEXT(2). Expected Mon_I=3, Skipped_I=0, Landed_I=1. The old generator segfaults.
  • Unittest_INHERIT_DIAPHRAGM: Diaphragm (INHERIT Slit). Expected 0.0625 behind it; the old generator gives 0.25 (no-op).
  • Unittest_USERVARS_TYPES: covers several USERVARS and DECLARE fixes, below. Expected AfterCheck_I=1, Flag_I=0.5.
    • an int user variable read by Monitor_nD;
    • several declarations on one line;
    • component USERVARS followed by EXTEND;
    • %include "x.hpp".
  • Unittest_GRAMMAR_ERRORS/*.instr.fail: six instruments that must fail code generation. These are MYSELF in parameters, bad JUMP targets, COPY of an undefined instance, an unknown component and an illegal pointer type. The README lists the observed old and new behaviour. They use .instr.fail so the test tools don't pick them up.

Expected values are in %Example lines. All tests pass with this branch on both flavours (macOS arm64).

Test Case Before (b478b4e32) After (this PR)
Unittest_JUMP_RELATIVE JUMP PREVIOUS(2) ITERATE 3, JUMP NEXT(2) code generator segfault Mon_I=3, Skipped_I=0, Landed_I=1
Unittest_INHERIT_DIAPHRAGM Diaphragm (INHERIT Slit) after a 0.5 m Slit AfterDiaphragm_I=0.251 (no-op) AfterDiaphragm_I=0.0627 (expected 0.0625)
Unittest_USERVARS_TYPES int USERVAR in Monitor_nD; several declarations per line; component USERVARS + EXTEND; %include "x.hpp" Cannot open include file 'unittest_uv.hpp.h' AfterCheck_I=1, Flag_I=0.4994 (expected 0.5)
Err_MYSELF_in_parameters Slit(xwidth=MYSELF) accepted, means the previous instance error: MYSELF can not be used here
Err_JUMP_unknown_target JUMP nosuch accepted, behaves as JUMP MYSELF error: target nosuch is not a component
Err_JUMP_out_of_range JUMP NEXT(2) past the last component accepted, jumps to component 2 error: target NEXT_2 is not a component
Err_COPY_undefined COPY(PREVIOUS) as first component code generator segfault error: COPY of an undefined component instance
Err_unknown_component unknown component class error, then segfault error, exit code 1
Err_illegal_pointer_type int *x instrument parameter Illegal type $s* for instrument parameter int at line x:83240208 Illegal type int* for instrument parameter x at line ...:4

Runs used 1e5 rays and seed 1, on macOS arm64.

@willend

willend commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

@g5t https://github.com/mccode-dev/McCode/actions/runs/37609263742/job/112752252161?pr=2767 gives a pretty clear indication of one issue? 😅

@willend

willend commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor Author

@g5t @farhi @mads-bertelsen another cogen-related edit is coming (added to this PR) relating to parsing of {}-lists in cogen.c

willend and others added 2 commits October 7, 2026 14:53
- Vector parameters given as {...} are now split at top-level commas and
  each element is written into the generated code as the C expression it
  is. Before, the elements were parsed with strtod, so an expression such
  as {1, 3.2*0.0219, ...} became {1, 3.2, 0, 0.0219, ...}: too many,
  shifted values. That affected IN15_Vpolariser/WASP_Vpolariser
  (Pol_guide_vmirror) in ILL_H5 and ILL_H5_new, which now get their
  intended reflectivity parameters: intensity downstream of these
  polarisers changes by factors of ~3-12 (results change, intended).
  Instrument parameters can now also be used in such vectors. All other
  shipped instruments generate the same values (literals are now copied
  as written, e.g. "1.0" instead of "1").
- Removed the "static vectors support literal numbers ONLY" warning,
  which no longer applies, and the unused copy of the old parser in
  pygen.c.in.
- FUNNEL: check the particle buffer allocations, and reset livebatchsize
  at the start of each batch (a SPLIT in the previous batch changes it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Document that vector parameters keep their expressions, and the
resulting change in ILL_H5/ILL_H5_new (IN15/WASP V-cavity polarisers).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@g5t g5t left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might be useful to allow cancelling INHERIT for specific sections without requiring a user to know that

%{
%}

means use the INHERIT'ed block while

%{

%}

means replace the INHERIT'ed section with nothing.

Comment thread mccode/src/instrument.y Outdated
Comment thread docs/GRAMMAR/ADR-records/ADR_20261007_GRAMMAR_FIXES.md Outdated
Review of PR #2767 (g5t): for DEFINE COMPONENT X INHERIT Y the generator
decided whether X provides a section by its number of lines, so an
explicitly written empty block (%{ %}) counted as "not written" and Y's
section was used, while the same block with a blank line inside
overrode it.

- Every way of writing a section (own %{..%}, INHERIT Z, EXTEND %{..%})
  now marks it present (codeblock_present: linenum > 0), and the INHERIT
  merge tests that. Same rule as mccode-antlr (mccode-antlr#321, #325).
  Within a section, parts are still concatenated in order; EXTEND never
  pulls in the parent implicitly.
- USERVARS now has the same form as the other sections and mccode-antlr:
  INHERIT allowed, repeated USERVARS keyword removed (unused).
- Merged blocks keep their %{ line/file, so SIG_MESSAGE debug strings now
  show the real location; otherwise all 450 examples generate identical
  code.
- New grammar test Unittest_INHERIT_OVERRIDE (McStas and McXtrace) and
  ADR update.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@willend

willend commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @g5t, agreed and implemented in e9c2c8b.

What was wrong

For DEFINE COMPONENT X INHERIT Y, the generator decided whether X provides a section by counting its lines. An explicitly written empty section such as

TRACE
%{
%}

counted as "not written", so Y's TRACE was used. The same block with a blank line inside overrode it, so whitespace decided.

The rule now (same as mccode-antlr, see mccode-dev/mccode-antlr#321 / #325)

  • Section written in X (even an empty %{ %}, or only INHERIT Z / EXTEND): it replaces Y's section. Every way of writing a section marks it as present, and the INHERIT merge checks that mark.
  • Section not written in X: taken from Y.
  • Within one section: SECTION [%{..%}] (INHERIT Z | EXTEND %{..%})*, with the parts concatenated in the order given.
  • EXTEND never pulls in Y's code implicitly: in X INHERIT Y, INITIALIZE EXTEND %{ more %} gives just more. Write INITIALIZE INHERIT Y EXTEND %{ more %} to get Y's code followed by more. That's what mccode-antlr does too (multi_block() concatenates in source order after blanking the inherited copy).
  • "Inherit everything but one section": write that section empty.
  • USERVARS: now has the same form as the other sections (INHERIT allowed), matching mccode-antlr. The old repeated-USERVARS form is gone; no shipped file used it.
  • Unchanged: COPY(instance) never copies EXTEND (COPYing a component instance should _not_ copy EXTEND if we follow upstream behavior mccode-antlr#330).

Tests (macOS arm64)

Test Before After
Unittest_INHERIT_OVERRIDE, empty TRACE %{ %} cancelling the parent's TRACE (McStas and McXtrace) AfterCancel_I=0.50 (parent's TRACE used) AfterCancel_I=1
Same test, child writing no sections / TRACE INHERIT parent EXTEND %{..%} – AfterKeep_I=0.50, AfterExtend_I=0.25
Simplified StatisticalChopper_Monitor from mccode-antlr#321 (only INITIALIZE INHERIT Monitor_nD EXTEND; no explicit DECLARE/TRACE/FINALLY/MCDISPLAY INHERIT) would not have inherited anything identical results to the shipped component
Generated code for all 450 McStas and McXtrace examples vs the previous commit – identical, except SIG_MESSAGE debug strings now show the real %{ file:line

The ADR has the rule and examples.

New child Unittest_IO_noinit writes only an empty INITIALIZE EXTEND
%{ %}: it keeps the parent's DECLARE and TRACE but not its INITIALIZE
(review of #2767). AfterNoInit_I = 0.125 (before the fix: 0.25, the
parent's INITIALIZE was used). McStas and McXtrace.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread mccode/src/cogen.c.in Outdated
Comment thread mccode/src/cogen.c.in Outdated
willend and others added 2 commits October 8, 2026 13:49
Review of #2767 (g5t): uservar_is_numeric() used strstr, so type names
containing "int", "char", ... (Point, struct interaction, charge_t)
counted as numeric and the generated (double) cast did not compile,
while unsigned/signed alone, size_t, bool/_Bool and <stdint.h> types
were reported as not readable.

- A USERVAR is numeric if it is not an array or pointer and every word
  of its type is one of char short int long float double signed
  unsigned _Bool bool const volatile MCNUM size_t, or matches
  u?int(_least|_fast)?N_t (same rule as is_numeric_scalar in
  mccode-antlr). Other typedefs are not recognised (ADR updated).
- particle_uservar_init() uses the same rule. It had the same substring
  test, and wrote p->arr[N]=0 (one past the end) for array USERVARS.
  char and long long USERVARS are now zeroed at start; they were
  uninitialised before.

All 452 examples generate identical code except 10 that now also zero
char/long long USERVARS; those 10 give identical detector results.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants