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
5 changes: 5 additions & 0 deletions dev-docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ The documentation changelog is kept separately: [CHANGELOG-DOCS](./CHANGELOG-DOC
### Code generation

- Reordered arguments of the `__tact_store_address_opt` function to optimize gas consumption: PR [#3333](https://github.com/tact-lang/tact/pull/3333)
- Don't load contract state unless getter reads or writes it: PR [#3364](https://github.com/tact-lang/tact/pull/3364)

### Release contributors

- [Petr Makhnev](https://github.com/i582)

## [1.6.13] - 2025-05-29

Expand Down
10 changes: 10 additions & 0 deletions src/benchmarks/jetton/size.json
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,16 @@
"wallet cells": "15",
"wallet bits": "7756"
}
},
{
"label": "1.6.13 without load contract state if it is not used",
"pr": "https://github.com/tact-lang/tact/pull/3364",
"size": {
"minter cells": "28",
"minter bits": "13865",
"wallet cells": "15",
"wallet bits": "7756"
}
}
]
}
10 changes: 10 additions & 0 deletions src/benchmarks/notcoin/size.json
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,16 @@
"wallet cells": "13",
"wallet bits": "8004"
}
},
{
"label": "1.6.13 without load contract state if it is not used",
"pr": "https://github.com/tact-lang/tact/pull/3364",
"size": {
"minter cells": "29",
"minter bits": "15807",
"wallet cells": "13",
"wallet bits": "8004"
}
}
]
}
48 changes: 0 additions & 48 deletions src/cli/unboc/__snapshots__/e2e.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,7 @@ PROGRAM{
130 THROW
}>
?fun_78250 PROC:<{
PUSHROOT
CTOS
1 LDI
DROP
<{
NULL
}> PUSHCONT
<{
NULL
}> PUSHCONT
IFELSE
?fun_ref_92183b49329bb4e4 INLINECALLDICT
NIP
}>
?fun_ref_92183b49329bb4e4 PROCREF:<{
x{68656C6C6F20776F726C64} PUSHSLICE
Expand Down Expand Up @@ -104,19 +92,7 @@ PROGRAM{
130 THROW // 0xF2C4_ 105_
}>
?fun_78250 PROC:<{
PUSHROOT // 0xED4 4
CTOS // 0xD0
1 LDI // 0xD2 00
DROP // 0x3 0
<{
NULL // 0x6D
}> PUSHCONT // 0x9 6D
<{
NULL // 0x6D
}> PUSHCONT // 0x9 6D
IFELSE // 0xE2
?fun_ref_92183b49329bb4e4 INLINECALLDICT // 0x
NIP // 0x3 1
}>
?fun_ref_92183b49329bb4e4 PROCREF:<{
x{68656C6C6F20776F726C64} PUSHSLICE // 0x8B 68656C6C6F20776F726C64
Expand Down Expand Up @@ -167,19 +143,7 @@ PROGRAM{
130 THROW
}>
?fun_78250 PROC:<{
c4 PUSHCTR
CTOS
1 LDI
s0 POP
<{
NULL
}> PUSHCONT
<{
NULL
}> PUSHCONT
IFELSE
?fun_ref_92183b49329bb4e4 INLINECALLDICT
s1 POP
}>
?fun_ref_92183b49329bb4e4 PROCREF:<{
x{68656C6C6F20776F726C64} PUSHSLICE
Expand Down Expand Up @@ -229,21 +193,9 @@ PROGRAM{
130 THROW
}>
?fun_78250 PROC:<{
PUSHROOT
CTOS
1 LDI
DROP
<{
NULL
}> PUSHCONT
<{
NULL
}> PUSHCONT
IFELSE
<{
x{68656C6C6F20776F726C64} PUSHSLICE
}>c CALLREF
NIP
}>
}END>c
",
Expand Down
46 changes: 34 additions & 12 deletions src/generator/writers/writeFunction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -714,25 +714,38 @@ export function writeFunction(f: FunctionDescription, ctx: WriterContext) {
? [getType(ctx.ctx, f.self.name), f.self.optional]
: [null, false];

const isGetterWithoutStateUsage =
f.isGetter &&
!f.effects.has("contractStorageRead") &&
!f.effects.has("contractStorageWrite");

// Write function header
let returns: string = resolveFuncType(f.returns, ctx);
const returnsOriginal = returns;
let returnsStr: string | null;
if (self && f.isMutating) {
if (f.returns.kind !== "void") {
if (isGetterWithoutStateUsage) {
returns = `((), ${returns})`;
} else if (f.returns.kind !== "void") {
returns = `(${resolveFuncType(self, ctx)}, ${returns})`;
} else {
returns = `(${resolveFuncType(self, ctx)}, ())`;
}
returnsStr = resolveFuncTypeUnpack(self, funcIdOf("self"), ctx);
returnsStr = isGetterWithoutStateUsage
? "()"
: resolveFuncTypeUnpack(self, funcIdOf("self"), ctx);
}

// Resolve function descriptor
const params: string[] = [];
if (self) {
params.push(
resolveFuncType(self, ctx, isSelfOpt) + " " + funcIdOf("self"),
);
if (isGetterWithoutStateUsage) {
params.push(`() ${funcIdOf("self")}`);
} else {
params.push(
resolveFuncType(self, ctx, isSelfOpt) + " " + funcIdOf("self"),
);
}
}

f.params.forEach((a, index) => {
Expand Down Expand Up @@ -816,7 +829,7 @@ export function writeFunction(f: FunctionDescription, ctx: WriterContext) {
}
ctx.body(() => {
// Unpack self
if (self && !isSelfOpt) {
if (self && !isSelfOpt && !isGetterWithoutStateUsage) {
ctx.append(
`var (${resolveFuncTypeUnpack(self, funcIdOf("self"), ctx)}) = ${funcIdOf("self")};`,
);
Expand Down Expand Up @@ -979,13 +992,22 @@ export function writeGetter(f: FunctionDescription, wCtx: WriterContext) {
);
}

// Load contract state
wCtx.append(`var self = ${ops.contractLoad(self.name, wCtx)}();`);
const call = `${wCtx.used(ops.extension(self.name, f.name))}(${f.params.map((v) => funcIdOf(v.name)).join(", ")})`;

// Execute get method
wCtx.append(
`var res = self~${wCtx.used(ops.extension(self.name, f.name))}(${f.params.map((v) => funcIdOf(v.name)).join(", ")});`,
);
if (
f.isGetter &&
!f.effects.has("contractStorageRead") &&
!f.effects.has("contractStorageWrite")
) {
wCtx.append(`var self = ();`);
wCtx.append(`var res = self~${call};`);
} else {
// Load contract state
wCtx.append(`var self = ${ops.contractLoad(self.name, wCtx)}();`);

// Execute get method
wCtx.append(`var res = self~${call};`);
}

// Pack if needed
if (f.returns.kind === "ref") {
Expand Down
2 changes: 1 addition & 1 deletion src/pipeline/__snapshots__/packaging.spec.ts.snap

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion src/pipeline/precompile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ import type * as Ast from "@/ast/ast";
import { getAstFactory } from "@/ast/ast-helpers";
import { getParser } from "@/grammar";
import { evalComptimeExpressions } from "@/types/evalComptimeExpressions";
import { computeReceiversEffects } from "@/types/effects";
import {
computeGettersEffects,
computeReceiversEffects,
} from "@/types/effects";
import { setAstFactoryToStore } from "@/pipeline/ast-factory-store";

export function precompile(
Expand Down Expand Up @@ -80,6 +83,7 @@ export function precompile(

// To use in code generation to decide if a receiver needs to call the contract storage function
computeReceiversEffects(ctx);
computeGettersEffects(ctx);

// Prepared context
return ctx;
Expand Down
1 change: 1 addition & 0 deletions src/storage/__snapshots__/resolveAllocation.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2262,6 +2262,7 @@ exports[`resolveAllocation should write program 1`] = `
"return": undefined,
"statements": [],
},
"effects": Set {},
"isAbstract": false,
"isGetter": false,
"isInline": false,
Expand Down
Loading