Conversation
This PR fixes a bug in the C code generator where importing the same module through multiple paths or with different visibility/meta flags caused it to be initialized multiple times at runtime. Previously these duplicate imports resulted in redundant C declarations and repeated initialization calls within the same function body. By deduplicating the list of initialization functions during the emission phase, the compiler now guarantees exactly one initialization call per module per phase. A regression test has been added to the module test suite to verify the absence of both duplicate C declarations and duplicate initialization calls within individual function scopes.
Author
|
awaiting-review |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR fixes a bug in the C code generator where importing the same module through multiple paths or with different visibility/meta flags caused it to be initialized multiple times at runtime. Previously these duplicate imports resulted in redundant C declarations and repeated initialization calls within the same function body. By deduplicating the list of initialization functions during the emission phase, the compiler now guarantees exactly one initialization call per module per phase. A regression test has been added to the module test suite to verify the absence of both duplicate C declarations and duplicate initialization calls within individual function scopes.
Context
I stumbled onto this while working through Functional Programming in Lean. I noticed identical lines and blocks of code in some
.lake/build/ir/*.cfiles when working with modules and traced this to a lack of deduplication between the function declarations and bodies during the C emission phase.Bug
When a module is imported via different visibility/meta flags the compiler would emit redundant declarations, and identical initialization calls in the same function scope. Steps to reproduce:
.lake/build/ir/Bug.cwith duplicates highlighted(expand/collapse)
.lake/build/ir/Bug/Basic.cwith duplicates highlighted:(expand/collapse)
Cause
src/Lean/Compiler/LCNF/EmitC.lean generates the C code. Its
main:and
emitInitFn:The uniqueness of the elements of
env.importsisn't checked, and since module identity is determined by name as shown byenv.getModuleIdx?this results in
impInitFnspotentially containing duplicate module names, and therefore multiple initializations being emitted within a function's body. The same issue occurs inemitLegacyInitFn:The duplicate declarations outside of function bodies occur because the calls to
emitInitFnandemitLegacyInitFninmaindon't share their work.Proposed Fix
impInitFnsbefore emitting.Implementation Notes
The factored
getInitFnusesList.eraseDupsto eliminate duplicates, and so does the newmainfunction:I initially used an$O(N^2)$ . I switched to the latter because the former makes the order of emitted C code non-deterministic, and because after examining $N$ is on the smaller side:
Std.HashSetfor the deduplication sinceList.eraseDupsisenv.imports.sizeacross the entire test suite (4215 tests at the time of writing),I'm not entirely sure how this compares to real-world Lean usage, so would appreciate feedback on that front.
The data was obtained by modifying
EmitC.lean::emitCForDecls:Testing
I added a regression test to
tests/lake/tests/module/test.shthat reuses the existingPromoteImportandPromoteMetaImportmodules to assert that no duplicate C declarations exist, and that no duplicateres = ...initialization calls exist within the same C function body usingawk. Crucially, all pre-existing tests still pass.Notes
static boolguard that prevents infinite recursion, but I'm confused by the need for the recursive call in the first place. Does anybody know why it's there?