Skip to content

Commit

Permalink
Merge branch 'main' into B4ckSl4sh/jettonUpdates
Browse files Browse the repository at this point in the history
# Conflicts:
#	src/test/benchmarks/escrow/results.json
#	src/test/benchmarks/jetton/results.json
  • Loading branch information
Shvandre committed Feb 28, 2025
2 parents ced8b8b + b55cfdf commit 02e25ed
Show file tree
Hide file tree
Showing 24 changed files with 1,146 additions and 43 deletions.
2 changes: 2 additions & 0 deletions dev-docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Don't show internal error for unknown types for variables: PR [#2005](https://github.com/tact-lang/tact/pull/2005)
- Struct serialization and parsing functions are now inlined more aggressively to save gas: PR [#2016](https://github.com/tact-lang/tact/pull/2016)
- `NOP` instructions and empty asm functions are now properly optimized: PR [#1959](https://github.com/tact-lang/tact/pull/1959)
- Contracts are now compiled with custom optimized function selector with a shortcut for `recv_internal` and `recv_external`: PR [#2038](https://github.com/tact-lang/tact/pull/2038)
- Contract receivers do not update the contract data cell at the end of execution if the receiver does not modify the contract storage: PR [#2067](https://github.com/tact-lang/tact/pull/2067)

### Fixed

Expand Down
46 changes: 46 additions & 0 deletions docs/src/content/docs/book/config.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,52 @@ Null checks are always enabled in the [`debug`](#options-debug) mode.
}
```

#### `optimizations` {#options-optimizations}

<Badge text="Available since Tact 1.6 (not released yet)" variant="tip" size="medium"/><p/>

Options that affect the optimizations of contracts.

```json filename="tact.config.json" {8,14}
{
"projects": [
{
"name": "contract",
"path": "./contract.tact",
"output": "./output",
"options": {
"optimizations": {}
}
}
]
}
```

##### `alwaysSaveContractData` {#optimizations-alwayssavecontractdata}

<Badge text="Available since Tact 1.6 (not released yet)" variant="tip" size="medium"/><p/>

`false{:json}` by default.

If set to `false{:json}`, enables saving the contract state at the end of a receiver execution if it modified the contract state, otherwise the contract data cell is not overwritten. Setting the option to `true{:json}` will result in each receiver updating the contract data cell regardless of contract state modifications thus resulting in an increase of gas consumption: this can be used for extra safety level or for debugging.

```json filename="tact.config.json" {9,17}
{
"projects": [
{
"name": "contract",
"path": "./contract.tact",
"output": "./output",
"options": {
"optimizations": {
"alwaysSaveContractData": false
}
}
}
]
}
```

#### `enableLazyDeploymentCompletedGetter` {#options-enablelazydeploymentcompletedgetter}

<Badge text="Available since Tact 1.6 (not released yet)" variant="tip" size="medium"/><p/>
Expand Down
2 changes: 2 additions & 0 deletions spell/cspell-fift-words-adjusted.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
2dup
2over
2swap
atend
Base
Base
Bcmp
Expand Down Expand Up @@ -174,6 +175,7 @@ pfxdict
pick
pop
pos
procdictkeylen
priv
pub
push
Expand Down
4 changes: 4 additions & 0 deletions src/cli/unboc/__snapshots__/e2e.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ PROGRAM{
78250 DECLMETHOD ?fun_78250
DECLPROC ?fun_ref_92183b49329bb4e4
recv_internal PROC:<{
DROP
DROP
CTOS
TWO
Expand Down Expand Up @@ -74,6 +75,7 @@ PROGRAM{
78250 DECLMETHOD ?fun_78250
DECLPROC ?fun_ref_92183b49329bb4e4
recv_internal PROC:<{
DROP // 0x3 0
DROP // 0x3 0
CTOS // 0xD0
TWO // 0x7 2
Expand Down Expand Up @@ -137,6 +139,7 @@ PROGRAM{
78250 DECLMETHOD ?fun_78250
DECLPROC ?fun_ref_92183b49329bb4e4
recv_internal PROC:<{
s0 POP
s0 POP
CTOS
2 PUSHINT
Expand Down Expand Up @@ -199,6 +202,7 @@ PROGRAM{
DECLPROC recv_internal
78250 DECLMETHOD ?fun_78250
recv_internal PROC:<{
DROP
DROP
CTOS
TWO
Expand Down
14 changes: 14 additions & 0 deletions src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ export type SafetyOptions = {
readonly nullChecks?: boolean;
};

export type OptimizationOptions = {
/**
* If set to `false`, updates the contract storage if a receiver modifies it, otherwise updates the contract storage in any case. Default is `false`.
* The analysis is conservative and might still update the storage in some tricky cases even if it wasn't modified.
*
* Read more: https://docs.tact-lang.org/book/config#alwayssavecontractdata
*/
readonly alwaysSaveContractData?: boolean;
};

export type ExperimentalOptions = {
/**
* If set to true, enables inlining of all functions in contracts.
Expand Down Expand Up @@ -56,6 +66,10 @@ export type Options = {
* Safety options for the contract.
*/
readonly safety?: SafetyOptions;
/**
* Optimization options for the contract.
*/
readonly optimizations?: OptimizationOptions;
/**
* If set to true, enables generation of `lazy_deployment_completed()` getter.
*/
Expand Down
9 changes: 9 additions & 0 deletions src/config/config.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ export const safetyOptionsSchema: z.ZodType<C.SafetyOptions> = z.object({
nullChecks: z.boolean().optional(),
});

export const optimizationOptionsSchema: z.ZodType<C.OptimizationOptions> =
z.object({
alwaysSaveContractData: z.boolean().optional(),
});

/**
* Per-project configuration options
*
Expand Down Expand Up @@ -57,6 +62,10 @@ export const optionsSchema: z.ZodType<C.Options> = z.object({
* Safety options for the contract.
*/
safety: safetyOptionsSchema.optional(),
/**
* Optimization options for the contract.
*/
optimizations: optimizationOptionsSchema.optional(),
/**
* If set to true, enables generation of `lazy_deployment_completed()` getter.
*/
Expand Down
4 changes: 4 additions & 0 deletions src/config/features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ export function enabledNullChecks(ctx: CompilerContext) {
return featureEnabled(ctx, "nullChecks");
}

export function enabledAlwaysSaveContractData(ctx: CompilerContext) {
return featureEnabled(ctx, "alwaysSaveContractData");
}

export function enabledLazyDeploymentCompletedGetter(ctx: CompilerContext) {
return featureEnabled(ctx, "lazyDeploymentCompletedGetter");
}
Expand Down
39 changes: 38 additions & 1 deletion src/generator/writers/writeContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,10 @@ export function writeMainContract(
wCtx.append();
}

if (enabledLazyDeploymentCompletedGetter(wCtx.ctx)) {
if (
enabledLazyDeploymentCompletedGetter(wCtx.ctx) &&
contract.init?.kind !== "contract-params"
) {
// Deployed
wCtx.append(`_ lazy_deployment_completed() method_id {`);
wCtx.inIndent(() => {
Expand Down Expand Up @@ -478,5 +481,39 @@ export function writeMainContract(
);
});
}

wCtx.append(`() __tact_selector_hack_asm() impure asm """
@atend @ 1 {
execute current@ context@ current!
{
}END> b>
<{
SETCP0 DUP
IFNOTJMP:<{
DROP over <s ref@ 0 swap @procdictkeylen idict@ { "internal shortcut error" abort } ifnot @addop
}>`);

if (hasExternal) {
wCtx.append(`DUP -1 EQINT IFJMP:<{
DROP over <s ref@ -1 swap @procdictkeylen idict@ { "internal shortcut error" abort } ifnot @addop
}>`);
}

wCtx.append(`swap <s ref@
0 swap @procdictkeylen idict- drop
-1 swap @procdictkeylen idict- drop
65535 swap @procdictkeylen idict- drop
@procdictkeylen DICTPUSHCONST DICTIGETJMPZ 11 THROWARG
}> b>
} : }END>c
current@ context! current!
} does @atend !
""";`);

wCtx.append(`() __tact_selector_hack() method_id(65535) {
return __tact_selector_hack_asm();
}`);
});
}
31 changes: 22 additions & 9 deletions src/generator/writers/writeRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { funcIdOf } from "./id";
import { ops } from "./ops";
import { resolveFuncTypeUnpack } from "./resolveFuncTypeUnpack";
import { writeStatement } from "./writeFunction";
import type { AstNumber, AstReceiver, AstStatement } from "../../ast/ast";
import type { AstNumber, AstReceiver } from "../../ast/ast";
import {
throwCompilationError,
throwInternal,
Expand All @@ -20,6 +20,8 @@ import type { SrcInfo } from "../../grammar";
import { contractErrors } from "../../abi/errors";
import { resolveFuncTypeFromAbiUnpack } from "./resolveFuncTypeFromAbiUnpack";
import { getAllocation } from "../../storage/resolveAllocation";
import type { Effect } from "../../types/effects";
import { enabledAlwaysSaveContractData } from "../../config/features";

type ContractReceivers = {
readonly internal: Receivers;
Expand All @@ -40,6 +42,7 @@ type Receivers = {

type FallbackReceiver = {
selector: FallbackReceiverSelector;
effects: ReadonlySet<Effect>;
ast: AstReceiver;
};

Expand Down Expand Up @@ -132,7 +135,7 @@ export function writeNonBouncedRouter(
const emptyRcv = receivers.empty;
wCtx.append(";; Receive empty message");
wCtx.inBlock("if ((op == 0) & (in_msg_length <= 32))", () => {
writeReceiverBody(emptyRcv.ast.statements, contract, wCtx);
writeReceiverBody(emptyRcv, contract, wCtx);
});
}

Expand Down Expand Up @@ -192,7 +195,7 @@ function writeBinaryReceiver(
`var ${msgFields} = in_msg~${ops.reader(selector.type, "no-opcode", wCtx)}();`,
);

writeReceiverBody(binaryReceiver.ast.statements, contract, wCtx);
writeReceiverBody(binaryReceiver, contract, wCtx);
});
}

Expand Down Expand Up @@ -267,7 +270,7 @@ function writeCommentReceivers(
wCtx.append(`;; Receive "${commentRcv.selector.comment}" message`);

wCtx.inBlock(`if (text_op == 0x${hash})`, () => {
writeReceiverBody(commentRcv.ast.statements, contract, wCtx);
writeReceiverBody(commentRcv, contract, wCtx);
});
});

Expand Down Expand Up @@ -326,12 +329,14 @@ export function groupContractReceivers(
case "internal-comment-fallback":
contractReceivers.internal.commentFallback = {
selector,
effects: receiver.effects,
ast: receiver.ast,
};
break;
case "internal-fallback":
contractReceivers.internal.fallback = {
selector,
effects: receiver.effects,
ast: receiver.ast,
};
break;
Expand All @@ -347,12 +352,14 @@ export function groupContractReceivers(
case "external-comment-fallback":
contractReceivers.external.commentFallback = {
selector,
effects: receiver.effects,
ast: receiver.ast,
};
break;
case "external-fallback":
contractReceivers.external.fallback = {
selector,
effects: receiver.effects,
ast: receiver.ast,
};
break;
Expand All @@ -362,6 +369,7 @@ export function groupContractReceivers(
case "bounce-fallback":
contractReceivers.bounced.fallback = {
selector,
effects: receiver.effects,
ast: receiver.ast,
};
break;
Expand Down Expand Up @@ -446,7 +454,7 @@ function writeFallbackReceiver(
wCtx: WriterContext,
): void {
wCtx.append(`slice ${funcIdOf(fbRcv.selector.name)} = ${inMsg};`);
writeReceiverBody(fbRcv.ast.statements, contract, wCtx);
writeReceiverBody(fbRcv, contract, wCtx);
}

function writeBouncedReceiver(
Expand Down Expand Up @@ -481,19 +489,24 @@ function writeBouncedReceiver(
: ops.reader(selector.type, "no-opcode", wCtx);
wCtx.append(`var ${msgFields} = in_msg~${msgReader}();`);

writeReceiverBody(bouncedReceiver.ast.statements, contract, wCtx);
writeReceiverBody(bouncedReceiver, contract, wCtx);
});
}

function writeReceiverBody(
statements: readonly AstStatement[],
rcv: ReceiverDescription,
contract: TypeDescription,
wCtx: WriterContext,
): void {
for (const stmt of statements) {
for (const stmt of rcv.ast.statements) {
writeStatement(stmt, null, null, wCtx);
}
writeStoreContractVariables(contract, wCtx);
if (
enabledAlwaysSaveContractData(wCtx.ctx) ||
rcv.effects.has("contractStorageWrite")
) {
writeStoreContractVariables(contract, wCtx);
}
wCtx.append("return ();");
}

Expand Down
5 changes: 5 additions & 0 deletions src/pipeline/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ export function enableFeatures(
option: config.options.safety?.nullChecks ?? true,
name: "nullChecks",
},
{
option:
config.options.optimizations?.alwaysSaveContractData ?? false,
name: "alwaysSaveContractData",
},
{
option: config.options.enableLazyDeploymentCompletedGetter ?? false,
name: "lazyDeploymentCompletedGetter",
Expand Down
4 changes: 4 additions & 0 deletions src/pipeline/precompile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { VirtualFileSystem } from "../vfs/VirtualFileSystem";
import type { AstModule } from "../ast/ast";
import type { FactoryAst } from "../ast/ast-helpers";
import type { Parser } from "../grammar";
import { computeReceiversEffects } from "../types/effects";

export function precompile(
ctx: CompilerContext,
Expand Down Expand Up @@ -42,6 +43,9 @@ export function precompile(
// This creates allocations for all defined types
ctx = resolveAllocations(ctx);

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

// Prepared context
return ctx;
}
Loading

0 comments on commit 02e25ed

Please sign in to comment.