Skip to content

feat(codegen): don't load contract state unless getter reads or writes it #3364

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

Open
wants to merge 15 commits into
base: main
Choose a base branch
from

Conversation

i582
Copy link
Contributor

@i582 i582 commented Jun 5, 2025

Closes #3363.

@i582 i582 requested a review from a team as a code owner June 5, 2025 13:54
@i582 i582 mentioned this pull request Jun 5, 2025
3 tasks
Copy link
Member

@Gusarich Gusarich left a comment

Choose a reason for hiding this comment

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

contract Test {
    get fun zero(): Int {      // does not read state
        return 0;
    }

    get fun bugTrigger() {
        self.zero();        // invokes zero() via the non-mutating wrapper
    }
}

->

FunC compilation error: /Users/daniil/Coding/tact/test.tact_Test.fc:63:28: error: cannot apply function $Test$_fun_zero : () -> ((), int) to arguments of type tuple: cannot unify type tuple with ()
      $self~$Test$_fun_zero();

@Gusarich
Copy link
Member

This optimization can incorrectly skip generating contract_load() for getters that indirectly use initOf or codeOf via static functions. Currently, computeGettersEffects() only considers effects from the arguments of static_call and does not analyze the body of called static functions. As a result, it might misclassify getters as storage-independent when they're actually dependent on storage initialization.

Example that breaks with current implementation:

contract Child {
    fun justForCodesDict(): StateInit {
        return initOf Test();
    }
}

fun helper(): StateInit {
    // requires __tact_child_contract_codes initialized by $contract_load
    return initOf Child();
}

contract Test {
    x: Int = 0;

    receive() {
        self.x += 1;
    }

    get fun broken(): Cell {
        // computeGettersEffects sees no storage access here
        return helper().code;
    }
}

You should enhance the analysis to recursively examine the bodies of static functions invoked by getters, ensuring proper detection of storage dependencies and preventing runtime errors caused by omitted contract_load().

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.

Don't load contract state unless getter reads or writes it
3 participants