Skip to content

Commit 1755289

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.
1 parent a26fbcd commit 1755289

4 files changed

Lines changed: 284 additions & 146 deletions

File tree

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

0 commit comments

Comments
 (0)