Skip to content

Make constants data location more powerful#16560

Open
k06a wants to merge 1 commit intoargotorg:developfrom
k06a:feature/non-integral-constants
Open

Make constants data location more powerful#16560
k06a wants to merge 1 commit intoargotorg:developfrom
k06a:feature/non-integral-constants

Conversation

@k06a
Copy link
Copy Markdown

@k06a k06a commented Mar 28, 2026

Feature: Constant Composite Types (DataLocation::Constant)

Summary

This PR extends the constant keyword to support composite types — structs, arrays (static and dynamic), arrays of strings, structs with string fields, arrays of structs, enum arrays, internal function pointer arrays, and dynamic arrays with slicing support. Previously, only value types and bytes/string were allowed as constant.

Additionally, constant is introduced as a new data location specifier for parameters of internal/private functions, enabling zero-copy passing of constant references (analogous to calldata).

Compilation is supported via the IR pipeline (--via-ir). The legacy codegen emits a clear error directing users to use --via-ir.

Motivation

Today, defining compile-time lookup tables, configs, or dispatch tables in Solidity requires either:

  • Assembly with hardcoded data
  • Storage slots (expensive at runtime)
  • Per-call memory allocation and initialization

With this change, developers can write:

struct Config { uint256 fee; address router; }
Config constant DEFAULT_CONFIG = Config(42, address(0x1234));

uint256[3] constant WEIGHTS = [uint256(10), 20, 30];

uint256[] constant TABLE = [uint256(1), 2, 3, 4, 5];
uint256 x = TABLE[2:4][0]; // slicing works

string[3] constant LABELS = ["alpha", "beta", "gamma"];

function add1(uint256 x) internal pure returns (uint256) { return x + 1; }
function mul2(uint256 x) internal pure returns (uint256) { return x * 2; }
function(uint256) internal pure returns (uint256)[2] constant OPS = [add1, mul2];

The constant data is evaluated once and accessed with standard Solidity syntax — no assembly, no storage, no per-call copies.

What's Supported

Type Example Status
Static arrays of value types uint256[3] constant Supported
Dynamic arrays of value types uint256[] constant Supported (2-slot stack: offset + length)
Dynamic array slicing DATA[s:e], DATA[s:e][i] Supported (like calldata)
Structs with value fields struct { uint, address } constant Supported
Nested structs struct { Inner, uint } constant Supported
Arrays of structs Point[2] constant Supported
Struct containing static array struct { uint[3], address } constant Supported
String arrays string[3] constant Supported
Struct with string fields struct { string, uint } constant Supported
Nested arrays uint256[2][3] constant Supported
Enum arrays Color[3] constant Supported
Internal function pointer arrays function()[3] constant Supported
constant as function param location function f(Config constant cfg) internal Supported
Mappings mapping(...) constant Rejected

Design Decisions

New DataLocation::Constant

A new enum value DataLocation::Constant is added to the type system. Constant composites behave like calldata arrays:

  • Static constant arrays and structs: 1 stack slot (codeOffset) — a memory pointer to the evaluated constant data
  • Dynamic constant arrays (uint256[]): 2 stack slots (codeOffset, length) — matching calldata dynamic array layout. The codeOffset points directly to elements in memory (no length prefix), and length is on the stack
  • bytes/string constants: 1 stack slot (memory pointer with length prefix) — unchanged from existing behavior

Bytecode-Embedded Constants (Data Section)

For "flat" constant composites (value-type-only arrays and structs without nested reference types), the initializer data is serialized at compile time and stored as a data "cdo_<id>" hex"..." block in the Yul object. At runtime, a single datacopy loads the blob into memory:

function constant_WEIGHTS_12() -> ret_codeOffset {
    ret_codeOffset := allocate_memory(96)
    datacopy(ret_codeOffset, dataoffset("cdo_WEIGHTS_12"), datasize("cdo_WEIGHTS_12"))
}

Types with pointer-based memory layouts (string arrays, nested structs, structs with array fields) fall back to runtime evaluation via the existing constantValueFunction() mechanism.

Constant-Index Folding

When accessing arr[0] or cfg.fee where the base is a constant variable and the index/member resolves to a compile-time known value, the IR generator folds the access to a literal constant directly, eliminating all memory operations:

// arr[0] where arr = [uint256(100), 200, 300]
// Folded to: 0x64  (no datacopy, no mload)
let expr_19 := 0x64

Dynamic Array Slicing

Dynamic constant arrays support index range access (slicing), matching calldata semantics:

uint256[] constant DATA = [uint256(10), 20, 30, 40, 50];
uint256 x = DATA[1:4][0]; // = 20

The slice produces a 2-slot ArraySliceType value (offset, length) with bounds checking, and supports nested slicing DATA[s:e][ss:ee][i].

constant as Function Parameter Location

The parser recognizes constant as a data location specifier for function parameters (only in internal/private functions, analogous to storage). This enables passing constant references without copying:

function process(Config constant cfg) internal pure returns (uint256) {
    return cfg.fee;  // zero-copy access
}

Internal Function Pointer Support

Internal function definitions are accepted as compile-time constant expressions in constant initializers via the isCompileTimeConstantExpression() helper in TypeChecker.cpp. This is narrowly scoped — only identifiers referencing FunctionDefinition nodes with FunctionType::Kind::Internal are accepted, not function pointer variables or function calls.

Changes

Source (20 files, +448 -42 lines)

  • Types.h — Added DataLocation::Constant to enum
  • Types.cpp — Propagated Constant through all DataLocation switch statements; 2-slot makeStackItems for dynamic constant arrays (excluding bytes/string); isImplicitlyConvertibleTo for constant arrays (static-to-dynamic allowed); ArraySliceType conversion for Constant
  • AST.h — Added Location::Constant to VariableDeclaration::Location enum
  • AST.cpp — Added Location::Constant to allowedDataLocations() for internal callable parameters
  • ASTJsonExporter.cpp / ASTJsonImporter.cpp — JSON serialization/deserialization for Constant location
  • Parser.cpp — Recognizes constant as a location specifier when allowLocationSpecifier is true (function parameters)
  • DeclarationTypeChecker.cpp — Lifted restriction on constant composites (arrays, structs allowed; mappings rejected); maps Location::Constant to DataLocation::Constant
  • TypeChecker.cppisCompileTimeConstantExpression() for internal function pointer references; slicing allowed for constant dynamic arrays; index access on constant slices
  • IRGeneratorForStatements.cppconstantValueFunction with multi-return assignment, data section optimization, constant-index folding; index access for static/dynamic/slice constant arrays; struct member access with folding; IndexRangeAccess for constant slicing
  • IRGenerationContext.h — Constant data objects registry for bytecode-embedded data
  • IRGenerator.cpp — Emits data "cdo_<id>" hex"..." blocks in Yul object template
  • Common.h/cppIRNames::constantDataObjectName() helper
  • YulUtilFunctions.h/cpparrayLengthFunction uses stack length for constant dynamic; arrayConversionFunction 2-slot support; memoryArrayIndexRangeAccess helper; slice conversion assertion for Constant
  • ABIFunctions.cpp — Minor adjustments for constant location handling
  • ArrayUtils.cpp / CompilerUtils.cpp / ExpressionCompiler.cpp — Legacy codegen stubs with clear error: "Constant composite types require compilation via the IR pipeline (use --via-ir)."

Tests (53 files, +292 -75 lines)

12 new semantic tests (all compileViaYul: true):

  • constant_static_array.soluint256[3], index access + copy to memory
  • constant_struct.sol — struct with value fields (uint, address, bytes32)
  • constant_nested_struct.sol — nested structs, deep member access
  • constant_string_array.solstring[3], string element access
  • constant_struct_with_string.sol — struct with string field
  • constant_nested_array.soluint256[2][3], nested index access
  • constant_struct_with_array.sol — struct with uint256[3] field
  • constant_array_of_structs.solPoint[3], struct field access through array
  • constant_enum_array.solColor[3]
  • constant_function_pointers.sol — internal function pointer dispatch table
  • constant_parameter_passing.solconstant as function parameter location
  • constant_array_slicing.soluint256[] dynamic constant: .length, index access, slicing, nested slicing, bounds checking

41 updated syntax tests — Updated expected error messages to reflect the new "constant" location in error output for internal/private function parameters and slicing.

Test Results

Suite Passed Failed Skipped
Syntax 3490 0 61
Constant-specific 83/83 0 0
Semantic (full) 1567 1 (pre-existing clz.sol) 107
Total 5060+ 0 new 168

Backward Compatibility

  • 100% backward compatible — all existing constant value types and bytes/string constants continue to work unchanged
  • No storage usage, no ABI changes
  • New feature is opt-in (only activates when using composite types with constant)
  • Legacy codegen gracefully rejects with actionable error message

@github-actions
Copy link
Copy Markdown

Thank you for your contribution to the Solidity compiler! A team member will follow up shortly.

If you haven't read our contributing guidelines and our review checklist before, please do it now, this makes the reviewing process and accepting your contribution smoother.

If you have any questions or need our help, feel free to post them in the PR or talk to us directly on the #solidity-dev channel on Matrix.

@k06a k06a force-pushed the feature/non-integral-constants branch 5 times, most recently from 0096dfb to 14d345f Compare March 28, 2026 23:50
@k06a k06a force-pushed the feature/non-integral-constants branch from 14d345f to eee65b7 Compare March 28, 2026 23:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant