Skip to content

Commit 3cc5f59

Browse files
committed
feat(slang): state-variable getters, custom errors, and type metadata
Adds the getter/ module synthesizing a getter `sol.func` for each public state variable off slang's getter type: scalar (value and reference) reads, keyed mapping/array levels (`sol.map` / `sol.gep` with `no_panic_bounds`), struct leaves with non-returnable members skipped, and constant initializer folds. External getter calls (`c.publicVar(keys)` -> static `sol.ext_call`) and `try` over a getter dispatch through the new `ExternalCallee` classification, which admits functions and public state variables alike. `require(cond, E(..))` lowers custom errors through `sol.require`; a contract namespace delegates value and place access to its members (`C.x = v`, `Library.CONST`); `type(..)` metadata folds to constants: integer min/max, name, interfaceId, and creation/runtime code through `sol.object_code`; error and event `.selector` fold to the selector and topic constants. Immutable and transient getters are loud `unimplemented!` arms.
1 parent 2fe6176 commit 3cc5f59

31 files changed

Lines changed: 1322 additions & 115 deletions

solx-mlir/src/context/function/dispatch.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//!
2-
//! The attribute a `sol.func` is reached through.
2+
//! The internal dispatch attribute a `sol.func` carries.
33
//!
44
55
use slang_solidity_v2::ast::FunctionDefinition;
@@ -8,14 +8,16 @@ use slang_solidity_v2::ast::NodeId;
88

99
use crate::FunctionKind;
1010

11-
/// The attribute a `sol.func` is reached through. A dispatch identifier is never zero, which the
12-
/// dialect reserves for the null function pointer, since slang numbers nodes from one.
11+
/// The internal dispatch attribute a `sol.func` carries, if any.
1312
#[derive(Clone, Copy)]
1413
pub enum FunctionDispatch {
15-
/// The identifier an internal function pointer dispatches to.
14+
/// The identifier an internal function pointer dispatches to; never zero, which the dialect
15+
/// reserves for the null function pointer, since slang numbers nodes from one.
1616
Identifier(NodeId),
1717
/// The dialect kind of a constructor, fallback or receive function.
1818
Kind(FunctionKind),
19+
/// A synthesized state-variable getter, dispatched by its ABI selector alone.
20+
Getter,
1921
}
2022

2123
impl From<&FunctionDefinition> for FunctionDispatch {

solx-mlir/src/context/function/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ impl<'context> Function<'context> {
9393
FunctionDispatch::Kind(function_kind) => {
9494
operation_builder.kind(function_kind.attribute(context.melior))
9595
}
96+
FunctionDispatch::Getter => operation_builder,
9697
};
9798
if let Some(selector_value) = selector {
9899
operation_builder = operation_builder

solx-mlir/src/ir/attributes.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,16 @@ sol_dialect_attribute! {
4545
}
4646
}
4747

48+
impl StateMutability {
49+
/// Whether a callee of this mutability dispatches through a static call.
50+
pub fn is_static(self) -> bool {
51+
match self {
52+
Self::Pure | Self::View => true,
53+
Self::NonPayable | Self::Payable => false,
54+
}
55+
}
56+
}
57+
4858
impl From<FunctionMutability> for StateMutability {
4959
fn from(mutability: FunctionMutability) -> Self {
5060
match mutability {

solx-mlir/src/ir/mod.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,9 @@ sol_ops! {
157157
Value::code(address: value) -> value {
158158
CodeOperation.cont_addr(address).out(memory())
159159
}
160+
Value::object_code(object_name: str) -> value {
161+
ObjectCodeOperation.obj_name(str_attr(object_name)).out(memory())
162+
}
160163
Value::bare_call(address: value, gas: value, amount: value, input: value) -> values {
161164
BareCallOperation.addr(address).gas(gas).val(amount).inp(input)
162165
.status(boolean()).ret_data(memory())
@@ -265,9 +268,9 @@ sol_ops! {
265268
DeleteOperation.reference(self)
266269
}
267270

268-
Place::gep(self, index: value, element_type: ty) -> place {
271+
Place::gep | gep_no_panic_bounds (self, index: value, element_type: ty) -> place {
269272
GepOperation.base_addr(self).idx(index).addr(gep_of(element_type))
270-
}
273+
} flagged .no_panic_bounds;
271274
Place::map(self, key: value, entry_type: ty) -> place {
272275
MapOperation.mapping(self).key(key).addr(entry_type)
273276
}

solx-mlir/src/ir/type/mod.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,12 @@ impl<'context> Type<'context> {
195195
IntegerType::try_from(self.inner).is_ok()
196196
}
197197

198-
/// The bit width of this integer type. Panics on a non-integer type, so a caller must first
199-
/// establish the type is an integer through [`Self::is_integer`].
198+
/// Whether this is a signed integer type.
199+
pub fn is_signed_integer(self) -> bool {
200+
IntegerType::try_from(self.inner).is_ok_and(|integer| integer.is_signed())
201+
}
202+
203+
/// The bit width of this integer type.
200204
pub fn integer_bit_width(self) -> u32 {
201205
IntegerType::try_from(self.inner)
202206
.expect("integer_bit_width called on a non-integer type")

solx-mlir/src/ir/value.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,26 @@ impl<'context> Value<'context> {
6464
Self::constant_from_bigint(&BigInt::one(), result_type, context)
6565
}
6666

67+
/// Materialises the smallest value the integer `result_type` admits: `type(T).min`.
68+
pub fn minimum(result_type: Type<'context>, context: &Context<'context>) -> Self {
69+
let value = if result_type.is_signed_integer() {
70+
-(BigInt::one() << (result_type.integer_bit_width() - 1))
71+
} else {
72+
BigInt::zero()
73+
};
74+
Self::constant_from_bigint(&value, result_type, context)
75+
}
76+
77+
/// Materialises the largest value the integer `result_type` admits: `type(T).max`.
78+
pub fn maximum(result_type: Type<'context>, context: &Context<'context>) -> Self {
79+
let value = if result_type.is_signed_integer() {
80+
(BigInt::one() << (result_type.integer_bit_width() - 1)) - 1
81+
} else {
82+
(BigInt::one() << result_type.integer_bit_width()) - 1
83+
};
84+
Self::constant_from_bigint(&value, result_type, context)
85+
}
86+
6787
/// Materialises an `i1` boolean constant.
6888
pub fn boolean(value: bool, context: &Context<'context>) -> Self {
6989
Self::constant(i64::from(value), Type::boolean(context.melior), context)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// RUN: solx --emit-mlir=sol %s | FileCheck %s
2+
3+
// Assignment to a contract-qualified state variable (C.x = v): solc's
4+
// print-init crashes (SIGSEGV), so this is solx-only.
5+
6+
// CHECK: sol.func @{{.*setNamespace.*}}
7+
// CHECK: %[[V:.*]] = sol.load %{{[0-9]+}} : !sol.ptr<ui256, Stack>, ui256
8+
// CHECK: %[[SLOT:.*]] = sol.addr_of @{{x.*}} : !sol.ptr<ui256, Storage>
9+
// CHECK: sol.store %[[V]], %[[SLOT]] : ui256, !sol.ptr<ui256, Storage>
10+
11+
contract C {
12+
uint256 x;
13+
14+
function setNamespace(uint256 v) public { C.x = v; }
15+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// RUN: solx --emit-mlir=sol %s | FileCheck %s
2+
3+
// Error and event .selector are compile-time constants: solc's print-init
4+
// crashes (SIGSEGV), so this is solx-only.
5+
6+
// CHECK: sol.func @{{.*error_selector.*}}
7+
// CHECK: sol.constant 816952677 : ui32
8+
// CHECK: sol.bytes_cast %{{.*}} : ui32 to !sol.fixedbytes<4>
9+
10+
// CHECK: sol.func @{{.*event_selector.*}}
11+
// CHECK: sol.constant 48926247962583432353061649299097379571640188309989729032230735130183305912964 : ui256
12+
// CHECK: sol.bytes_cast %{{.*}} : ui256 to !sol.fixedbytes<32>
13+
14+
error MyError(uint256 x);
15+
16+
contract C {
17+
event MyEvent(uint256 indexed a);
18+
19+
function error_selector() external pure returns (bytes4) {
20+
return MyError.selector;
21+
}
22+
23+
function event_selector() external pure returns (bytes32) {
24+
return MyEvent.selector;
25+
}
26+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// RUN: solx --emit-mlir=sol %s | FileCheck %s
2+
// RUN: solc --mlir-action=print-init %s 2>/dev/null | FileCheck %s
3+
4+
// CHECK: %[[T:.*]] = sol.this : !sol.contract<{{.*}}>
5+
// CHECK: %[[A:.*]] = sol.address_cast %[[T]] : !sol.contract<{{.*}}> to !sol.address
6+
// CHECK: sol.ext_call "{{.*}}"() at %[[A]] gas %{{.*}} value %{{.*}} selector %{{.*}} {callee_type = () -> ui256, static_call} : !sol.address, () -> (i1, ui256)
7+
8+
// CHECK: %[[T:.*]] = sol.this : !sol.contract<{{.*}}>
9+
// CHECK: %[[A:.*]] = sol.address_cast %[[T]] : !sol.contract<{{.*}}> to !sol.address
10+
// CHECK: sol.ext_call "{{.*}}"(%{{.*}}) at %[[A]] gas %{{.*}} value %{{.*}} selector %{{.*}} {callee_type = (ui256) -> ui256, static_call} : !sol.address, (ui256) -> (i1, ui256)
11+
12+
// CHECK: %[[T:.*]] = sol.this : !sol.contract<{{.*}}>
13+
// CHECK: %[[A:.*]] = sol.address_cast %[[T]] : !sol.contract<{{.*}}> to !sol.address
14+
// CHECK: sol.ext_call "{{.*}}"(%{{.*}}) at %[[A]] gas %{{.*}} value %{{.*}} selector %{{.*}} {callee_type = (ui256) -> ui256, static_call} : !sol.address, (ui256) -> (i1, ui256)
15+
16+
// CHECK: %[[T:.*]] = sol.this : !sol.contract<{{.*}}>
17+
// CHECK: %[[A:.*]] = sol.address_cast %[[T]] : !sol.contract<{{.*}}> to !sol.address
18+
// CHECK: %{{.*}}, %[[R:.*]]:2 = sol.ext_call "{{.*}}"() at %[[A]] gas %{{.*}} value %{{.*}} selector %{{.*}} {callee_type = () -> (ui256, ui256), static_call} : !sol.address, () -> (i1, ui256, ui256)
19+
// CHECK: sol.return %[[R]]#0, %[[R]]#1 : ui256, ui256
20+
21+
// CHECK: sol.ext_call "{{.*}}"(%{{.*}}) at %{{.*}} gas %{{.*}} value %{{.*}} selector %{{.*}} {callee_type = (ui256) -> ui256, static_call} : !sol.address, (ui256) -> (i1, ui256)
22+
// CHECK: sol.ext_call "{{.*}}"(%{{.*}}) at %{{.*}} gas %{{.*}} value %{{.*}} selector %{{.*}} {callee_type = (ui256) -> ui256, static_call} : !sol.address, (ui256) -> (i1, ui256)
23+
24+
contract Elementary {
25+
uint256 public x;
26+
27+
function g() external returns (uint256) {
28+
return this.x();
29+
}
30+
}
31+
32+
contract Mapping {
33+
mapping(uint256 => uint256) public m;
34+
35+
function g(uint256 k) external returns (uint256) {
36+
return this.m(k);
37+
}
38+
}
39+
40+
contract Sequence {
41+
uint256[] public array;
42+
43+
function g(uint256 i) external returns (uint256) {
44+
return this.array(i);
45+
}
46+
}
47+
48+
contract Struct {
49+
struct S { uint256 a; uint256 b; }
50+
51+
S public s;
52+
53+
function g() external returns (uint256, uint256) {
54+
return this.s();
55+
}
56+
}
57+
58+
contract Token {
59+
mapping(uint256 => uint256) public m;
60+
uint256[] public array;
61+
}
62+
63+
contract Viewer {
64+
function readArray(Token o, uint256 index) external view returns (uint256) {
65+
return o.array(index);
66+
}
67+
68+
function readMapping(Token o, uint256 key) external view returns (uint256) {
69+
return o.m(key);
70+
}
71+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// RUN: solx --emit-mlir=sol %s | FileCheck %s
2+
// RUN: solc --mlir-action=print-init %s 2>/dev/null | FileCheck %s
3+
4+
// CHECK: sol.func @{{.*}} attributes {{.*}}kind = #{{.*}}Constructor
5+
6+
// CHECK: sol.func @{{.*}} attributes {{.*}}kind = #{{.*}}Receive, state_mutability = #{{.*}}Payable
7+
8+
// CHECK: sol.func @{{.*}} attributes {{.*}}kind = #{{.*}}Fallback{{.*}}state_mutability = #{{.*}}Payable
9+
10+
// CHECK: sol.func @{{.*}}() attributes {{.*}}kind = #Fallback{{.*}}state_mutability = #NonPayable
11+
// CHECK: sol.get_calldata : !sol.string<CallData>
12+
// CHECK: sol.length %{{.*}} : !sol.string<CallData>
13+
// CHECK: sol.store %{{.*}}, %{{.*}} : ui256, !sol.ptr<ui256, Storage>
14+
15+
// CHECK: sol.func @{{.*}}() attributes {{.*}}kind = #Fallback{{.*}}state_mutability = #NonPayable
16+
// CHECK: sol.sig : !sol.fixedbytes<4>
17+
// CHECK: sol.store %{{.*}}, %{{.*}} : !sol.fixedbytes<4>, !sol.ptr<!sol.fixedbytes<4>, Storage>
18+
19+
// CHECK: sol.func @{{.*}}() attributes {{.*}}kind = #Fallback{{.*}}state_mutability = #Payable
20+
// CHECK: %[[VALUE:.*]] = sol.callvalue : ui256
21+
// CHECK: sol.store %[[VALUE]], %{{.*}} : ui256, !sol.ptr<ui256, Storage>
22+
23+
// CHECK: sol.func @{{.*}}() attributes {{.*}}kind = #Receive{{.*}}state_mutability = #Payable
24+
// CHECK: %[[VALUE:.*]] = sol.callvalue : ui256
25+
// CHECK: sol.store %[[VALUE]], %{{.*}} : ui256, !sol.ptr<ui256, Storage>
26+
27+
contract AllKinds {
28+
uint256 x;
29+
30+
constructor(uint256 val) {
31+
x = val;
32+
}
33+
34+
receive() external payable {}
35+
36+
fallback() external payable {}
37+
38+
function get() public view returns (uint256) {
39+
return x;
40+
}
41+
}
42+
43+
contract MsgDataLength {
44+
uint256 public lastLength;
45+
46+
fallback() external {
47+
lastLength = msg.data.length;
48+
}
49+
}
50+
51+
contract MsgSig {
52+
bytes4 public lastSignature;
53+
54+
fallback() external {
55+
lastSignature = msg.sig;
56+
}
57+
}
58+
59+
contract PayableFallback {
60+
uint256 public received;
61+
62+
fallback() external payable {
63+
received = msg.value;
64+
}
65+
}
66+
67+
contract PayableReceive {
68+
uint256 public total;
69+
70+
receive() external payable {
71+
total = msg.value;
72+
}
73+
}

0 commit comments

Comments
 (0)