Skip to content

Commit 6dc32a6

Browse files
committed
feat(slang): narrow literal types via slang's evaluator
Replaces solx's digit-counting width heuristic and hand-built MLIR attribute strings with slang's CompileConstantEvaluator and LiteralKind width/sign info. Unifies integer constant emission through Builder::emit_constant(&BigInt, Type), which dispatches to sol.address_cast, arith.constant, or sol.constant by target type. Narrows signed negative literals to their minimum signed width, and lowers rational decimal literals (1.5 ether, 0.5 gwei) that reduce to integers after unit multiplication.
1 parent 672db86 commit 6dc32a6

12 files changed

Lines changed: 438 additions & 132 deletions

File tree

Cargo.lock

Lines changed: 28 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

solx-mlir/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ cc = "1.2.60"
1111

1212
[dependencies]
1313
anyhow.workspace = true
14+
num.workspace = true
1415
melior = { git = "https://github.com/NomicFoundation/melior", rev = "62a909e2ff67fcc19f7f42dc4fb70c3e03376bd2", features = ["ods-dialects", "helpers"] }
1516
# mlir-sys 210 wraps LLVM 21.0 C API; llvm-sys (via inkwell) targets 21.1.
1617
# Both are built from the same LLVM source tree (LLVM_SYS_211_PREFIX ==

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

Lines changed: 43 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ use melior::ir::attribute::TypeAttribute;
2727
use melior::ir::operation::OperationLike;
2828
use melior::ir::r#type::FunctionType;
2929
use melior::ir::r#type::IntegerType;
30+
use num::BigInt;
3031

3132
use crate::CmpPredicate;
3233
use crate::StateMutability;
@@ -223,92 +224,77 @@ impl<'context> Builder<'context> {
223224
.into()
224225
}
225226

226-
/// Emits a `sol.constant` of the given type from a decimal string.
227+
/// Emits a typed integer constant, selecting the dialect by target type.
227228
///
228-
/// # Errors
229+
/// `i1` is the signless boolean type owned by the arith dialect; every
230+
/// other integer type is signed or unsigned and belongs to the sol
231+
/// dialect. This is the single entry point for MLIR integer constants
232+
/// that carry a `BigInt`-sized value.
233+
///
234+
/// # Panics
229235
///
230-
/// Returns an error if the string cannot be parsed as an MLIR integer attribute.
231-
pub fn emit_sol_constant_from_decimal_str<'block, B>(
236+
/// Panics if the MLIR attribute parser rejects the `{value} : {type}`
237+
/// rendering. For well-typed callers this is unreachable because the
238+
/// type is a melior `IntegerType` and the value is a `BigInt` that
239+
/// always has a canonical decimal representation.
240+
pub fn emit_constant<'block, B>(
232241
&self,
233-
value: &str,
242+
value: &BigInt,
234243
result_type: Type<'context>,
235244
block: &B,
236-
) -> anyhow::Result<Value<'context, 'block>>
245+
) -> Value<'context, 'block>
237246
where
238247
B: BlockLike<'context, 'block>,
239248
'context: 'block,
240249
{
250+
if result_type == self.types.sol_address {
251+
let integer = self.emit_constant(value, self.types.ui160, block);
252+
return self.emit_sol_address_cast(integer, result_type, block);
253+
}
254+
if TypeFactory::integer_bit_width(result_type) == solx_utils::BIT_LENGTH_BOOLEAN as u32 {
255+
let boolean_attribute =
256+
IntegerAttribute::new(result_type, i64::from(*value != BigInt::ZERO)).into();
257+
return block
258+
.append_operation(melior::dialect::arith::constant(
259+
self.context,
260+
boolean_attribute,
261+
self.unknown_location,
262+
))
263+
.result(0)
264+
.expect("arith.constant always produces one result")
265+
.into();
266+
}
267+
// melior does not expose a `BigInt`-accepting attribute constructor,
268+
// so we round-trip through the MLIR attribute parser to avoid
269+
// truncating values wider than 64 bits.
241270
let attribute = Attribute::parse(self.context, &format!("{value} : {result_type}"))
242-
.ok_or_else(|| anyhow::anyhow!("invalid {result_type} decimal literal: {value}"))?;
271+
.expect("BigInt value and melior integer type always parse as an MLIR attribute");
243272
self.emit_constant_operation(attribute, result_type, block)
273+
.expect("well-typed BigInt constant never fails emission")
244274
}
245275

246-
/// Emits a `sol.constant` of the given type from a hex string (without `0x` prefix).
247-
///
248-
/// # Errors
249-
///
250-
/// Returns an error if the string cannot be parsed as an MLIR integer attribute.
251-
pub fn emit_sol_constant_from_hex_str<'block, B>(
252-
&self,
253-
hexadecimal: &str,
254-
result_type: Type<'context>,
255-
block: &B,
256-
) -> anyhow::Result<Value<'context, 'block>>
276+
/// Emits an `i1` boolean constant.
277+
pub fn emit_bool<'block, B>(&self, value: bool, block: &B) -> Value<'context, 'block>
257278
where
258279
B: BlockLike<'context, 'block>,
259280
'context: 'block,
260281
{
261-
let attribute = Attribute::parse(self.context, &format!("0x{hexadecimal} : {result_type}"))
262-
.ok_or_else(|| anyhow::anyhow!("invalid {result_type} hex literal: 0x{hexadecimal}"))?;
263-
self.emit_constant_operation(attribute, result_type, block)
282+
self.emit_constant(&BigInt::from(u8::from(value)), self.types.i1, block)
264283
}
265284

266285
/// Emits an all-ones `sol.constant` for the given integer type.
267-
///
268-
/// # Errors
269-
///
270-
/// Returns an error if the constant cannot be parsed as an MLIR integer attribute.
271286
pub fn emit_sol_constant_all_ones<'block, B>(
272287
&self,
273288
integer_type: Type<'context>,
274289
block: &B,
275-
) -> anyhow::Result<Value<'context, 'block>>
276-
where
277-
B: BlockLike<'context, 'block>,
278-
'context: 'block,
279-
{
280-
let bit_width = TypeFactory::integer_bit_width(integer_type);
281-
let all_ones_hex = "f".repeat(bit_width as usize / 4);
282-
self.emit_sol_constant_from_hex_str(&all_ones_hex, integer_type, block)
283-
}
284-
285-
/// Emits an `arith.constant` for a signless `i1` boolean value.
286-
///
287-
/// Boolean (`i1`) is signless in MLIR, so it uses `arith.constant`
288-
/// instead of `sol.constant` (which is for signed/unsigned int types).
289-
///
290-
/// # Panics
291-
///
292-
/// Panics if the MLIR operation cannot be constructed.
293-
pub fn emit_arith_constant_bool<'block, B>(
294-
&self,
295-
value: bool,
296-
block: &B,
297290
) -> Value<'context, 'block>
298291
where
299292
B: BlockLike<'context, 'block>,
300293
'context: 'block,
301294
{
302-
let i1_type = IntegerType::new(self.context, solx_utils::BIT_LENGTH_BOOLEAN as u32).into();
303-
block
304-
.append_operation(melior::dialect::arith::constant(
305-
self.context,
306-
IntegerAttribute::new(i1_type, i64::from(value)).into(),
307-
self.unknown_location,
308-
))
309-
.result(0)
310-
.expect("arith.constant always produces one result")
311-
.into()
295+
let bit_width = TypeFactory::integer_bit_width(integer_type);
296+
let all_ones = (BigInt::from(1u32) << bit_width) - BigInt::from(1u32);
297+
self.emit_constant(&all_ones, integer_type, block)
312298
}
313299

314300
// ==== Terminators ====

solx-slang/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@ doctest = false
1111

1212
[dependencies]
1313
anyhow.workspace = true
14+
num.workspace = true
1415
serde_json.workspace = true
1516
semver.workspace = true
1617
# `__private_testing_utils` is required by `__private_backend_api` internals
1718
# (binder/types access in node_extensions). Both are unstable Slang APIs.
1819
# TODO: pin to a release tag instead of branch = "main"
19-
slang_solidity = { git = "https://github.com/NomicFoundation/slang.git", rev = "a01bc76b742173941cea1aff0b3b68a9006b38b8", features = ["__private_backend_api", "__private_testing_utils"] }
20+
slang_solidity = { git = "https://github.com/NomicFoundation/slang.git", rev = "68051e76eec565d2eec5deb41ae8c7a565340651", features = ["__private_backend_api", "__private_testing_utils"] }
2021
melior = { git = "https://github.com/NomicFoundation/melior", rev = "62a909e2ff67fcc19f7f42dc4fb70c3e03376bd2", features = ["ods-dialects", "helpers"] }
2122

2223
solx-codegen-evm = { path = "../solx-codegen-evm" }

solx-slang/src/ast/contract/function/expression/arithmetic.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ impl<'state, 'context, 'block> ExpressionEmitter<'state, 'context, 'block> {
105105
let all_ones = self
106106
.state
107107
.builder
108-
.emit_sol_constant_all_ones(operand_type, &block)?;
108+
.emit_sol_constant_all_ones(operand_type, &block);
109109
let result = block
110110
.append_operation(
111111
XorOperation::builder(
@@ -132,7 +132,7 @@ impl<'state, 'context, 'block> ExpressionEmitter<'state, 'context, 'block> {
132132
.state
133133
.builder
134134
.emit_sol_cmp(value, zero, CmpPredicate::Eq, &block);
135-
let result_type = target_type.unwrap_or_else(|| self.state.builder.types.ui256);
135+
let result_type = target_type.unwrap_or(self.state.builder.types.ui256);
136136
let result = TypeConversion::from_target_type(result_type, &self.state.builder)
137137
.emit(cmp, &self.state.builder, &block);
138138
Ok((result, block))

solx-slang/src/ast/contract/function/expression/call/type_conversion.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use melior::ir::Type;
66
use melior::ir::ValueLike;
77
use melior::ir::r#type::IntegerType;
8+
use slang_solidity::backend::ir::ast::LiteralKind;
89
use slang_solidity::backend::ir::ast::StateVariableDefinition;
910
use slang_solidity::backend::ir::ast::Type as SlangType;
1011

@@ -41,7 +42,33 @@ impl<'context> TypeConversion<'context> {
4142
solx_utils::BIT_LENGTH_BOOLEAN as u32,
4243
)),
4344
SlangType::Address(_) => builder.types.sol_address,
44-
SlangType::Literal(_) => builder.types.ui256,
45+
SlangType::Literal(literal_type) => match literal_type.kind() {
46+
LiteralKind::Zero => Type::from(IntegerType::unsigned(
47+
builder.context,
48+
solx_utils::BIT_LENGTH_BYTE as u32,
49+
)),
50+
LiteralKind::Address => builder.types.sol_address,
51+
LiteralKind::DecimalInteger {
52+
bytes,
53+
signed: true,
54+
} => {
55+
let bits = bytes * solx_utils::BIT_LENGTH_BYTE as u32;
56+
Type::from(IntegerType::signed(builder.context, bits))
57+
}
58+
LiteralKind::DecimalInteger {
59+
bytes,
60+
signed: false,
61+
}
62+
| LiteralKind::HexInteger { bytes } => {
63+
let bits = bytes * solx_utils::BIT_LENGTH_BYTE as u32;
64+
Type::from(IntegerType::unsigned(builder.context, bits))
65+
}
66+
kind @ (LiteralKind::Rational
67+
| LiteralKind::HexString { .. }
68+
| LiteralKind::String { .. }) => {
69+
panic!("MLIR type resolution is not supported for literal kind {kind:?}")
70+
}
71+
},
4572
_ => unimplemented!("unsupported Slang type"),
4673
}
4774
}

solx-slang/src/ast/contract/function/expression/logical.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ impl<'state, 'context, 'block> ExpressionEmitter<'state, 'context, 'block> {
6767

6868
let i1_type = self.state.builder.types.i1;
6969
let result_ptr = self.state.builder.emit_sol_alloca(i1_type, &block);
70-
let false_value = self.state.builder.emit_arith_constant_bool(false, &block);
70+
let false_value = self.state.builder.emit_bool(false, &block);
7171
self.state
7272
.builder
7373
.emit_sol_store(false_value, result_ptr, &block);
@@ -111,7 +111,7 @@ impl<'state, 'context, 'block> ExpressionEmitter<'state, 'context, 'block> {
111111

112112
let i1_type = self.state.builder.types.i1;
113113
let result_ptr = self.state.builder.emit_sol_alloca(i1_type, &block);
114-
let true_value = self.state.builder.emit_arith_constant_bool(true, &block);
114+
let true_value = self.state.builder.emit_bool(true, &block);
115115
self.state
116116
.builder
117117
.emit_sol_store(true_value, result_ptr, &block);

0 commit comments

Comments
 (0)