Repository navigation
-mm=own: @record beside @lends carries a borrowing record across modules - #680
Merged
Merged
Conversation
…modules A declaration seen from another module has no body to say whether its result is a borrowing record (own spec 32) - a box the caller owns - or a plain borrow of an argument. The importer took it for a plain borrow and never freed the box. `@record` beside `@lends` says it: - MLIRGen reads it (OWN_DECLARED_RECORD_ATTR_NAME, a unit attribute) on functions and methods; the declaration printer emits it after `@lends`. - The importer's declaration has it: meetFacts and, for a call nothing resolves, calleesDeclare give the call `__own_result_record`, and under own OwnedReturnConsumptionPass counts its result owned (calleeDeclaresRecord: by name, through a dynamic import's global, or as a method), so the box is released where the caller lets it go. Before, no release was emitted at all. - checkDeclared: `@record` without `@lends`, or on a body that returns a borrow as it is, is an error; an exported function of a library whose body returns a record has to say it. An override says the same `@record`. Guessing record-ness from the result type or from several `@lends` names is not safe: a caller told it owns a record would free an argument. Spec 32 updated. Tests: export_own_record / import_own_record (-shared, gc rc own), import_own_record_outlives, own-shared-record.cmake (the importer releases each box), three negatives; own_lends's exported pairOf says @record. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # tslang/test/tester/CMakeLists.txt
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.
What
A new
@recordmarker, written beside@lends. It says the result is a borrowing record (own spec 32): a box the caller owns that holds a borrow of each argument@lendsnames. Without it,@lendsalone means a plain borrow of an argument. Another module sees only the declaration, so the marker is the only way it can tell the two apart.Why
Spec 32 documented a leak: "A declaration's
@lends, seen from another module without its body, is a plain borrow: a record's box then leaks at that caller." The importer could not tell, and inferring record-ness from the result type, or from@lendsnaming several parameters, is not safe. A plain borrow of an interface argument looks the same, and a caller told it owns a record would free that argument, which is a double free rather than a leak.How
@recordon functions and methods intots.own_declared_record, a unit attribute. The declaration printer emits it after@lends, and the dynamic-import global carries it.meetFactsgives a declaration's calls__own_result_recordwhen the declaration says@record.calleesDeclaredoes the same for a call nothing resolves.@recordgets an owned result (calleeDeclaresRecord). The callee may be named directly, read through a dynamic import's global, or called as a method. This half is what closes the leak. MLIRGen never treats an imported call's result as owned, so before this change the importer emitted no release of the box at all.checkDeclared, in the defining module:@recordwithout@lendsis an error.@recordon a body that returns its argument as it is, is an error.@record. A function no other module calls does not need to: its callers go by its body.@recordas what it overrides; the pinned message ofown_err_marker_overridenow lists@record.Tests
export_own_record/import_own_recordrun with-sharedunder gc, rc and own, covering both a function and a method that return records.import_own_record_outliveschecks that reading the record after an argument it borrows is reassigned is still refused.own-shared-record.cmake(Windows) checks that the importer's IR releases eachPairbox. Before the consumption-pass change there was no release.own_err_record_without_lends,own_err_record_not_record,own_err_record_exported_unsaid.own_lends.tsexportspairOf, a record function, so it now says@record. The DefaultLib uses no@lends.Not covered
A call to another module's method that isn't on the library's no-drops list counts as one that may free what the caller borrows. So in the importer, a record still can't be read across such a call. That is unchanged by this PR;
import_own_recordcalls the function and the method in separate loops so the two don't interact.🤖 Generated with Claude Code