Skip to content

Commit 7f982d2

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 7f982d2

4 files changed

Lines changed: 318 additions & 146 deletions

File tree

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
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(
89+
&condition,
90+
message.as_deref(),
91+
block,
92+
)?))
93+
}
94+
}
95+
}
96+
97+
/// Emits a member access expression as an EVM intrinsic.
98+
///
99+
/// Resolves the member via slang's binder to a specific `BuiltIn` variant
100+
/// and lowers it to the matching `sol.*` operation. Address-base intrinsics
101+
/// (`address.balance`, `address.codehash`) first evaluate the address
102+
/// operand and pass it as the operation's container address.
103+
///
104+
/// # Errors
105+
///
106+
/// Returns an error if the member access does not resolve to a recognized
107+
/// EVM intrinsic.
108+
pub(super) fn emit_built_in_member_access(
109+
&self,
110+
access: &MemberAccessExpression,
111+
block: BlockRef<'context, 'block>,
112+
) -> anyhow::Result<(Value<'context, 'block>, BlockRef<'context, 'block>)> {
113+
let builder = &self.expression_emitter.state.builder;
114+
match access.member().resolved_built_in() {
115+
Some(BuiltIn::AddressBalance) => {
116+
self.emit_address_base_intrinsic(access, block, |address_value| {
117+
BalanceOperation::builder(builder.context, builder.unknown_location)
118+
.cont_addr(address_value)
119+
.out(builder.types.ui256)
120+
.build()
121+
.into()
122+
})
123+
}
124+
Some(BuiltIn::AddressCodehash) => {
125+
self.emit_address_base_intrinsic(access, block, |address_value| {
126+
CodeHashOperation::builder(builder.context, builder.unknown_location)
127+
.cont_addr(address_value)
128+
.out(builder.types.ui256)
129+
.build()
130+
.into()
131+
})
132+
}
133+
resolved => {
134+
let operation = match resolved {
135+
Some(BuiltIn::TxOrigin) => {
136+
OriginOperation::builder(builder.context, builder.unknown_location)
137+
.addr(builder.types.sol_address)
138+
.build()
139+
.into()
140+
}
141+
Some(BuiltIn::TxGasPrice) => {
142+
GasPriceOperation::builder(builder.context, builder.unknown_location)
143+
.val(builder.types.ui256)
144+
.build()
145+
.into()
146+
}
147+
Some(BuiltIn::MsgSender) => {
148+
CallerOperation::builder(builder.context, builder.unknown_location)
149+
.addr(builder.types.sol_address)
150+
.build()
151+
.into()
152+
}
153+
Some(BuiltIn::MsgValue) => {
154+
CallValueOperation::builder(builder.context, builder.unknown_location)
155+
.val(builder.types.ui256)
156+
.build()
157+
.into()
158+
}
159+
Some(BuiltIn::BlockTimestamp) => {
160+
TimestampOperation::builder(builder.context, builder.unknown_location)
161+
.val(builder.types.ui256)
162+
.build()
163+
.into()
164+
}
165+
Some(BuiltIn::BlockNumber) => {
166+
BlockNumberOperation::builder(builder.context, builder.unknown_location)
167+
.val(builder.types.ui256)
168+
.build()
169+
.into()
170+
}
171+
Some(BuiltIn::BlockCoinbase) => {
172+
CoinbaseOperation::builder(builder.context, builder.unknown_location)
173+
.addr(builder.types.sol_address)
174+
.build()
175+
.into()
176+
}
177+
Some(BuiltIn::BlockChainid) => {
178+
ChainIdOperation::builder(builder.context, builder.unknown_location)
179+
.val(builder.types.ui256)
180+
.build()
181+
.into()
182+
}
183+
Some(BuiltIn::BlockBasefee) => {
184+
BaseFeeOperation::builder(builder.context, builder.unknown_location)
185+
.val(builder.types.ui256)
186+
.build()
187+
.into()
188+
}
189+
Some(BuiltIn::BlockGaslimit) => {
190+
GasLimitOperation::builder(builder.context, builder.unknown_location)
191+
.val(builder.types.ui256)
192+
.build()
193+
.into()
194+
}
195+
Some(BuiltIn::BlockBlobbasefee) => {
196+
BlobBaseFeeOperation::builder(builder.context, builder.unknown_location)
197+
.val(builder.types.ui256)
198+
.build()
199+
.into()
200+
}
201+
Some(BuiltIn::BlockDifficulty) => {
202+
DifficultyOperation::builder(builder.context, builder.unknown_location)
203+
.val(builder.types.ui256)
204+
.build()
205+
.into()
206+
}
207+
Some(BuiltIn::BlockPrevrandao) => {
208+
PrevRandaoOperation::builder(builder.context, builder.unknown_location)
209+
.val(builder.types.ui256)
210+
.build()
211+
.into()
212+
}
213+
_ => anyhow::bail!("unsupported member access: {}", access.member().name()),
214+
};
215+
let value = block
216+
.append_operation(operation)
217+
.result(0)
218+
.expect("intrinsic always produces one result")
219+
.into();
220+
Ok((value, block))
221+
}
222+
}
223+
}
224+
225+
/// Emits an EVM intrinsic that reads from a per-address container, e.g.
226+
/// `address.balance` (`sol.balance`) or `address.codehash` (`sol.code_hash`).
227+
///
228+
/// Evaluates the address operand, builds the operation via `build_op`, and
229+
/// extracts its single result.
230+
fn emit_address_base_intrinsic<F>(
231+
&self,
232+
access: &MemberAccessExpression,
233+
block: BlockRef<'context, 'block>,
234+
build_op: F,
235+
) -> anyhow::Result<(Value<'context, 'block>, BlockRef<'context, 'block>)>
236+
where
237+
F: FnOnce(Value<'context, 'block>) -> Operation<'context>,
238+
{
239+
let (address_value, block) = self
240+
.expression_emitter
241+
.emit_value(&access.operand(), block)?;
242+
let value = block
243+
.append_operation(build_op(address_value))
244+
.result(0)
245+
.expect("address-base intrinsic always produces one result")
246+
.into();
247+
Ok((value, block))
248+
}
249+
250+
/// Emits an `assert(condition)` built-in via `sol.assert`.
251+
fn emit_assert(
252+
&self,
253+
condition: &Expression,
254+
block: BlockRef<'context, 'block>,
255+
) -> anyhow::Result<BlockRef<'context, 'block>> {
256+
let (condition_value, block) = self.expression_emitter.emit_value(condition, block)?;
257+
let condition_boolean = self
258+
.expression_emitter
259+
.emit_is_nonzero(condition_value, &block);
260+
self.expression_emitter
261+
.state
262+
.builder
263+
.emit_sol_assert(condition_boolean, &block);
264+
Ok(block)
265+
}
266+
267+
/// Emits a `require(condition)` or `require(condition, "message")` built-in
268+
/// via `sol.require`.
269+
fn emit_require(
270+
&self,
271+
condition: &Expression,
272+
message: Option<&str>,
273+
block: BlockRef<'context, 'block>,
274+
) -> anyhow::Result<BlockRef<'context, 'block>> {
275+
let (condition_value, block) = self.expression_emitter.emit_value(condition, block)?;
276+
let condition_boolean = self
277+
.expression_emitter
278+
.emit_is_nonzero(condition_value, &block);
279+
self.expression_emitter
280+
.state
281+
.builder
282+
.emit_sol_require(condition_boolean, message, &block);
283+
Ok(block)
284+
}
285+
}

0 commit comments

Comments
 (0)