Skip to content

Commit 7e3db46

Browse files
committed
feat(slang): lower additional EVM intrinsics
Wire up `block.blobbasefee`, `block.difficulty`, `block.prevrandao`, `address.balance`, and `address.codehash` member accesses to their Sol dialect ops. The address-base intrinsics evaluate the operand expression and emit `sol.balance` / `sol.code_hash` with the resulting address value. Built-in dispatch is routed through slang's `BuiltIn` binder for both member access and call sites. The lit test for EVM context is extended with the four new intrinsics; codehash coverage waits on bytes32 lowering.
1 parent e590ced commit 7e3db46

5 files changed

Lines changed: 329 additions & 147 deletions

File tree

solx-mlir/tests/lit/evm_context.sol

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,18 @@
3030
// CHECK: sol.func @"get_gaslimit()"
3131
// CHECK: sol.gaslimit : ui256
3232

33+
// CHECK: sol.func @"get_blobbasefee()"
34+
// CHECK: sol.blobbasefee : ui256
35+
36+
// CHECK: sol.func @"get_difficulty()"
37+
// CHECK: sol.difficulty : ui256
38+
39+
// CHECK: sol.func @"get_prevrandao()"
40+
// CHECK: sol.prevrandao : ui256
41+
42+
// CHECK: sol.func @"get_balance(address)"
43+
// CHECK: sol.balance %{{.*}} : !sol.address -> ui256
44+
3345
contract C {
3446
function get_sender() public view returns (address) {
3547
return msg.sender;
@@ -70,4 +82,20 @@ contract C {
7082
function get_gaslimit() public view returns (uint256) {
7183
return block.gaslimit;
7284
}
85+
86+
function get_blobbasefee() public view returns (uint256) {
87+
return block.blobbasefee;
88+
}
89+
90+
function get_difficulty() public view returns (uint256) {
91+
return block.difficulty;
92+
}
93+
94+
function get_prevrandao() public view returns (uint256) {
95+
return block.prevrandao;
96+
}
97+
98+
function get_balance(address a) public view returns (uint256) {
99+
return a.balance;
100+
}
73101
}
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
//!
2+
//! Solidity built-in function and EVM intrinsic lowering.
3+
//!
4+
5+
use melior::ir::BlockLike;
6+
use melior::ir::BlockRef;
7+
use melior::ir::Operation;
8+
use melior::ir::Value;
9+
use slang_solidity::backend::built_ins::BuiltIn;
10+
use slang_solidity::backend::ir::ast::Expression;
11+
use slang_solidity::backend::ir::ast::MemberAccessExpression;
12+
use slang_solidity::backend::ir::ast::PositionalArguments;
13+
use solx_mlir::ods::sol::BalanceOperation;
14+
use solx_mlir::ods::sol::BaseFeeOperation;
15+
use solx_mlir::ods::sol::BlobBaseFeeOperation;
16+
use solx_mlir::ods::sol::BlockNumberOperation;
17+
use solx_mlir::ods::sol::CallValueOperation;
18+
use solx_mlir::ods::sol::CallerOperation;
19+
use solx_mlir::ods::sol::ChainIdOperation;
20+
use solx_mlir::ods::sol::CodeHashOperation;
21+
use solx_mlir::ods::sol::CoinbaseOperation;
22+
use solx_mlir::ods::sol::DifficultyOperation;
23+
use solx_mlir::ods::sol::GasLimitOperation;
24+
use solx_mlir::ods::sol::GasPriceOperation;
25+
use solx_mlir::ods::sol::OriginOperation;
26+
use solx_mlir::ods::sol::PrevRandaoOperation;
27+
use solx_mlir::ods::sol::TimestampOperation;
28+
29+
use crate::ast::contract::function::expression::call::CallEmitter;
30+
31+
impl<'emitter, 'state, 'context, 'block> CallEmitter<'emitter, 'state, 'context, 'block> {
32+
/// Tries to emit `callee(arguments)` as a Solidity built-in.
33+
///
34+
/// Resolves the callee via slang's binder to a [`BuiltIn`] variant.
35+
/// Returns `Ok(Some(block))` if the call resolves to a recognized
36+
/// built-in and was lowered; returns `Ok(None)` if the callee is not a
37+
/// built-in and the caller should fall through to generic dispatch.
38+
///
39+
/// # Errors
40+
///
41+
/// Returns an error if the callee is a built-in but its arguments are
42+
/// malformed (e.g. non-string `require` message).
43+
pub fn try_emit_built_in_call(
44+
&self,
45+
callee: &Expression,
46+
arguments: &PositionalArguments,
47+
block: BlockRef<'context, 'block>,
48+
) -> anyhow::Result<Option<BlockRef<'context, 'block>>> {
49+
let Expression::Identifier(identifier) = callee else {
50+
return Ok(None);
51+
};
52+
let Some(built_in) = identifier.resolved_built_in() else {
53+
return Ok(None);
54+
};
55+
match built_in {
56+
BuiltIn::Assert if arguments.len() == 1 => {
57+
let condition = arguments.iter().next().expect("argument count verified");
58+
Ok(Some(self.emit_assert(&condition, block)?))
59+
}
60+
BuiltIn::Require if matches!(arguments.len(), 1 | 2) => {
61+
let mut iter = arguments.iter();
62+
let condition = iter.next().expect("argument count verified");
63+
let message = match iter.next() {
64+
Some(Expression::StringExpression(string_expression)) => {
65+
let bytes = string_expression.value();
66+
Some(String::from_utf8(bytes).expect("require message is valid UTF-8"))
67+
}
68+
Some(_) => anyhow::bail!("require message must be a string literal"),
69+
None => None,
70+
};
71+
Ok(Some(self.emit_require(
72+
&condition,
73+
message.as_deref(),
74+
block,
75+
)?))
76+
}
77+
_ => Ok(None),
78+
}
79+
}
80+
81+
/// Emits a member access expression as an EVM intrinsic.
82+
///
83+
/// Resolves the member via slang's binder to a specific `BuiltIn` variant
84+
/// and lowers it to the matching `sol.*` operation. Address-base intrinsics
85+
/// (`address.balance`, `address.codehash`) first evaluate the address
86+
/// operand and pass it as the operation's container address.
87+
///
88+
/// # Errors
89+
///
90+
/// Returns an error if the member access does not resolve to a recognized
91+
/// EVM intrinsic.
92+
pub fn emit_built_in_member_access(
93+
&self,
94+
access: &MemberAccessExpression,
95+
block: BlockRef<'context, 'block>,
96+
) -> anyhow::Result<(Value<'context, 'block>, BlockRef<'context, 'block>)> {
97+
let builder = &self.expression_emitter.state.builder;
98+
match access.member().resolved_built_in() {
99+
Some(BuiltIn::AddressBalance) => {
100+
self.emit_address_base_intrinsic(access, block, |address_value| {
101+
BalanceOperation::builder(builder.context, builder.unknown_location)
102+
.cont_addr(address_value)
103+
.out(builder.types.ui256)
104+
.build()
105+
.into()
106+
})
107+
}
108+
Some(BuiltIn::AddressCodehash) => {
109+
self.emit_address_base_intrinsic(access, block, |address_value| {
110+
CodeHashOperation::builder(builder.context, builder.unknown_location)
111+
.cont_addr(address_value)
112+
.out(builder.types.ui256)
113+
.build()
114+
.into()
115+
})
116+
}
117+
resolved => {
118+
let operation = match resolved {
119+
Some(BuiltIn::TxOrigin) => {
120+
OriginOperation::builder(builder.context, builder.unknown_location)
121+
.addr(builder.types.sol_address)
122+
.build()
123+
.into()
124+
}
125+
Some(BuiltIn::TxGasPrice) => {
126+
GasPriceOperation::builder(builder.context, builder.unknown_location)
127+
.val(builder.types.ui256)
128+
.build()
129+
.into()
130+
}
131+
Some(BuiltIn::MsgSender) => {
132+
CallerOperation::builder(builder.context, builder.unknown_location)
133+
.addr(builder.types.sol_address)
134+
.build()
135+
.into()
136+
}
137+
Some(BuiltIn::MsgValue) => {
138+
CallValueOperation::builder(builder.context, builder.unknown_location)
139+
.val(builder.types.ui256)
140+
.build()
141+
.into()
142+
}
143+
Some(BuiltIn::BlockTimestamp) => {
144+
TimestampOperation::builder(builder.context, builder.unknown_location)
145+
.val(builder.types.ui256)
146+
.build()
147+
.into()
148+
}
149+
Some(BuiltIn::BlockNumber) => {
150+
BlockNumberOperation::builder(builder.context, builder.unknown_location)
151+
.val(builder.types.ui256)
152+
.build()
153+
.into()
154+
}
155+
Some(BuiltIn::BlockCoinbase) => {
156+
CoinbaseOperation::builder(builder.context, builder.unknown_location)
157+
.addr(builder.types.sol_address)
158+
.build()
159+
.into()
160+
}
161+
Some(BuiltIn::BlockChainid) => {
162+
ChainIdOperation::builder(builder.context, builder.unknown_location)
163+
.val(builder.types.ui256)
164+
.build()
165+
.into()
166+
}
167+
Some(BuiltIn::BlockBasefee) => {
168+
BaseFeeOperation::builder(builder.context, builder.unknown_location)
169+
.val(builder.types.ui256)
170+
.build()
171+
.into()
172+
}
173+
Some(BuiltIn::BlockGaslimit) => {
174+
GasLimitOperation::builder(builder.context, builder.unknown_location)
175+
.val(builder.types.ui256)
176+
.build()
177+
.into()
178+
}
179+
Some(BuiltIn::BlockBlobbasefee) => {
180+
BlobBaseFeeOperation::builder(builder.context, builder.unknown_location)
181+
.val(builder.types.ui256)
182+
.build()
183+
.into()
184+
}
185+
Some(BuiltIn::BlockDifficulty) => {
186+
DifficultyOperation::builder(builder.context, builder.unknown_location)
187+
.val(builder.types.ui256)
188+
.build()
189+
.into()
190+
}
191+
Some(BuiltIn::BlockPrevrandao) => {
192+
PrevRandaoOperation::builder(builder.context, builder.unknown_location)
193+
.val(builder.types.ui256)
194+
.build()
195+
.into()
196+
}
197+
_ => anyhow::bail!("unsupported member access: {}", access.member().name()),
198+
};
199+
let value = block
200+
.append_operation(operation)
201+
.result(0)
202+
.expect("intrinsic always produces one result")
203+
.into();
204+
Ok((value, block))
205+
}
206+
}
207+
}
208+
209+
/// Emits an EVM intrinsic that reads from a per-address container, e.g.
210+
/// `address.balance` (`sol.balance`) or `address.codehash` (`sol.code_hash`).
211+
///
212+
/// Evaluates the address operand, builds the operation via `build_op`, and
213+
/// extracts its single result.
214+
fn emit_address_base_intrinsic<F>(
215+
&self,
216+
access: &MemberAccessExpression,
217+
block: BlockRef<'context, 'block>,
218+
build_op: F,
219+
) -> anyhow::Result<(Value<'context, 'block>, BlockRef<'context, 'block>)>
220+
where
221+
F: FnOnce(Value<'context, 'block>) -> Operation<'context>,
222+
{
223+
let (address_value, block) = self
224+
.expression_emitter
225+
.emit_value(&access.operand(), block)?;
226+
let value = block
227+
.append_operation(build_op(address_value))
228+
.result(0)
229+
.expect("address-base intrinsic always produces one result")
230+
.into();
231+
Ok((value, block))
232+
}
233+
234+
/// Emits an `assert(condition)` built-in via `sol.assert`.
235+
fn emit_assert(
236+
&self,
237+
condition: &Expression,
238+
block: BlockRef<'context, 'block>,
239+
) -> anyhow::Result<BlockRef<'context, 'block>> {
240+
let (condition_value, block) = self.expression_emitter.emit_value(condition, block)?;
241+
let condition_boolean = self
242+
.expression_emitter
243+
.emit_is_nonzero(condition_value, &block);
244+
self.expression_emitter
245+
.state
246+
.builder
247+
.emit_sol_assert(condition_boolean, &block);
248+
Ok(block)
249+
}
250+
251+
/// Emits a `require(condition)` or `require(condition, "message")` built-in
252+
/// via `sol.require`.
253+
fn emit_require(
254+
&self,
255+
condition: &Expression,
256+
message: Option<&str>,
257+
block: BlockRef<'context, 'block>,
258+
) -> anyhow::Result<BlockRef<'context, 'block>> {
259+
let (condition_value, block) = self.expression_emitter.emit_value(condition, block)?;
260+
let condition_boolean = self
261+
.expression_emitter
262+
.emit_is_nonzero(condition_value, &block);
263+
self.expression_emitter
264+
.state
265+
.builder
266+
.emit_sol_require(condition_boolean, message, &block);
267+
Ok(block)
268+
}
269+
}

0 commit comments

Comments
 (0)