Skip to content

Commit 4fc629e

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 a26fbcd commit 4fc629e

11 files changed

Lines changed: 423 additions & 119 deletions

File tree

Cargo.lock

Lines changed: 7 additions & 5 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: 51 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;
@@ -224,92 +225,85 @@ impl<'context> Builder<'context> {
224225
.into()
225226
}
226227

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

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

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

315309
// ==== String literals ====

solx-slang/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ semver.workspace = true
1616
# `__private_testing_utils` is required by `__private_backend_api` internals
1717
# (binder/types access in node_extensions). Both are unstable Slang APIs.
1818
# TODO: pin to a release tag instead of branch = "main"
19-
slang_solidity = { git = "https://github.com/NomicFoundation/slang.git", rev = "6f892dff8e2d6d275cfe9737c3cb364486c41410", features = ["__private_backend_api", "__private_testing_utils"] }
19+
slang_solidity = { git = "https://github.com/NomicFoundation/slang.git", rev = "d8388d6bf696383627ece31ba3551880d0b9eafc", features = ["__private_backend_api", "__private_testing_utils"] }
2020
melior = { git = "https://github.com/NomicFoundation/melior", rev = "62a909e2ff67fcc19f7f42dc4fb70c3e03376bd2", features = ["ods-dialects", "helpers"] }
2121

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

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

Lines changed: 1 addition & 1 deletion
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(

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

Lines changed: 30 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,35 @@ 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+
unimplemented!(
70+
"MLIR type resolution is not yet implemented for literal kind {kind:?}"
71+
)
72+
}
73+
},
4574
_ => unimplemented!("unsupported Slang type"),
4675
}
4776
}

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)