Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(lang-lexer): fix directive injection from multiple files #38

Closed
wants to merge 5 commits into from

Conversation

alixinne
Copy link
Owner

@alixinne alixinne commented Nov 2, 2023

See #24 for the actual fix and discussion, this only adds a few tests.

AlexTMjugador and others added 3 commits April 5, 2023 14:19
…ltiple files

As of now, injecting preprocessor directives found in included files back into
the AST when using the `v2-full` lexer is not supported. Attempting to do so
will trap the `Directives::inject` method in the following loop forever, as a
different source file ID will cause neither `directive_idx` to be incremented or
`break` to be reached:

https://github.com/vtavernier/glsl-lang/blob/881c0e472bfa990150c7a3fcd8b3efc787b66ccc/lang-lexer/src/v2_full/directives.rs#L134-L158

After giving the situation some thought, I think it's more appropriate to inject
the preprocessor directives that we are currently capable of injecting,
regardless of whether they come from a different file:

- `#pragma`: most of these directives are to be defined by the GLSL compiler
  implementation, and those documented in the GLSL specification change the
  compiler state in a way where only their location in the source is relevant,
  not the file they were expanded from.
- `#extension`: similar to `#pragma`. Furthermore, the specification explicitly
  states that "directives that occur later override those seen earlier".
- `#version`: the GLSL specification states that "the #version directive must
  occur in a shader before anything else". This suggests (but does not
  unequivocally assert) that there should only be one `#version` directive per
  shader.

By now, I think it is clear that `#pragma` and `#extension` can be injected as
they are without further ado. However, handling `#version` in combination with
`#include` requires further work, in my view.

Let's consider a shader A that is valid on its own, but does not contain a `void
main()` function (this is allowed by the GLSL specification, as long as the
shader is linked together with another shader that defines this function). Now,
consider a shader B that `#include`s shader A and defines `void main()`. Let's
assume that the GLSL application compiles shader B. In this scenario, I'd argue
that it makes sense for both A and B to have a `#version` directive in the
beginning of their respective sources, because both are shaders, according to
the specification. A could define a `void main()` function only if a
preprocessor directive set by B before the `#include` is unset, allowing it to
be linked as a completely standalone shader, where the `#version` directive
can't be placed anywhere else. Remarkably, [the specifications for `#include`
extensions like
`ARB_shading_language_include`](https://registry.khronos.org/OpenGL/extensions/ARB/ARB_shading_language_include.txt)
do not mention how to handle multiple `#version` directives at all.

Given the above scenario, I think that a good solution is to make sure that
`Directives::inject` only emits at most one `#version` directive, with its
version and profile set to the highest version and most featureful profile among
those specified in all `#version` directives. As far as I know, GLSL versions
are backwards compatible, and even if they weren't, several `#version`
directives are likely to violate the GLSL specification, so there would be no
way to reconcile GLSL sources targeting incompatible GLSL versions anyway.

These changes let `#pragma` and `#extension` directives coming from included
files be injected anyway. `#version` directives are injected too, but only one
`#version` directive is ever injected, according to the technique stated in the
previous paragraph.
…injected at wrong positions

Preprocessor directives from included files have different span
positions than directives in the top-level shader file. So a included
preprocessor directive may span from 0 to 100, while the #include
directive in the top-level file ends at position 50, which may cause the
included preprocessor directive to be injected after the first external
declaration that follows the #include directive that starts after
position 100, not at the #include position.

To fix that, bring back the source ID to the checks, relying on previous
include directive events to output the child directives at just the
right time.
@alixinne alixinne self-assigned this Nov 2, 2023
@alixinne alixinne closed this Jul 23, 2024
@alixinne alixinne deleted the fix/pr-24-with-tests branch July 23, 2024 21:16
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