Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,9 @@ jobs:
SOLX_LIT_TARGET: ${{ matrix.target || '' }}
run: |
LIT=llvm-lit${{ runner.os == 'Windows' && '.py' || '' }}
${LIT} -v solx-mlir/tests/lit/
# CMake bakes the configuring python's absolute path into the lit
# shebang; runner image updates move it, so pick python3 from PATH.
python3 "${RUNNER_TEMP}/lit-tools/${LIT}" -v solx-mlir/tests/lit/

actionlint:
name: Lint GitHub Actions workflows
Expand Down
14 changes: 7 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,4 @@ features = [
[workspace.dependencies.slang_solidity_v2]
git = "https://github.com/NomicFoundation/slang.git"
# TODO: pin to a release tag instead of a revision.
rev = "820f7c12c70ec2a97e4b2463e91e6c6ac68ad289"
rev = "ad18c38a0af62abbcfb77ea55087184aa0d1eba5"
10 changes: 6 additions & 4 deletions solx-mlir/src/context/function/dispatch.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//!
//! The attribute a `sol.func` is reached through.
//! The internal dispatch attribute a `sol.func` carries.
//!

use slang_solidity_v2::ast::FunctionDefinition;
Expand All @@ -8,14 +8,16 @@ use slang_solidity_v2::ast::NodeId;

use crate::FunctionKind;

/// The attribute a `sol.func` is reached through. A dispatch identifier is never zero, which the
/// dialect reserves for the null function pointer, since slang numbers nodes from one.
/// The internal dispatch attribute a `sol.func` carries, if any.
#[derive(Clone, Copy)]
pub enum FunctionDispatch {
/// The identifier an internal function pointer dispatches to.
/// The identifier an internal function pointer dispatches to; never zero, which the dialect
/// reserves for the null function pointer, since slang numbers nodes from one.
Identifier(NodeId),
/// The dialect kind of a constructor, fallback or receive function.
Kind(FunctionKind),
/// A synthesized state-variable getter, dispatched by its ABI selector alone.
Getter,
}

impl From<&FunctionDefinition> for FunctionDispatch {
Expand Down
1 change: 1 addition & 0 deletions solx-mlir/src/context/function/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ impl<'context> Function<'context> {
FunctionDispatch::Kind(function_kind) => {
operation_builder.kind(function_kind.attribute(context.melior))
}
FunctionDispatch::Getter => operation_builder,
};
if let Some(selector_value) = selector {
operation_builder = operation_builder
Expand Down
10 changes: 10 additions & 0 deletions solx-mlir/src/ir/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ sol_dialect_attribute! {
}
}

impl StateMutability {
/// Whether a callee of this mutability dispatches through a static call.
pub fn is_static(self) -> bool {
match self {
Self::Pure | Self::View => true,
Self::NonPayable | Self::Payable => false,
}
}
}

impl From<FunctionMutability> for StateMutability {
fn from(mutability: FunctionMutability) -> Self {
match mutability {
Expand Down
7 changes: 5 additions & 2 deletions solx-mlir/src/ir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,9 @@ sol_ops! {
Value::code(address: value) -> value {
CodeOperation.cont_addr(address).out(memory())
}
Value::object_code(object_name: str) -> value {
ObjectCodeOperation.obj_name(str_attr(object_name)).out(memory())
}
Value::bare_call(address: value, gas: value, amount: value, input: value) -> values {
BareCallOperation.addr(address).gas(gas).val(amount).inp(input)
.status(boolean()).ret_data(memory())
Expand Down Expand Up @@ -265,9 +268,9 @@ sol_ops! {
DeleteOperation.reference(self)
}

Place::gep(self, index: value, element_type: ty) -> place {
Place::gep | gep_no_panic_bounds (self, index: value, element_type: ty) -> place {
GepOperation.base_addr(self).idx(index).addr(gep_of(element_type))
}
} flagged .no_panic_bounds;
Place::map(self, key: value, entry_type: ty) -> place {
MapOperation.mapping(self).key(key).addr(entry_type)
}
Expand Down
8 changes: 6 additions & 2 deletions solx-mlir/src/ir/type/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,12 @@ impl<'context> Type<'context> {
IntegerType::try_from(self.inner).is_ok()
}

/// The bit width of this integer type. Panics on a non-integer type, so a caller must first
/// establish the type is an integer through [`Self::is_integer`].
/// Whether this is a signed integer type.
pub fn is_signed_integer(self) -> bool {
IntegerType::try_from(self.inner).is_ok_and(|integer| integer.is_signed())
}

/// The bit width of this integer type.
pub fn integer_bit_width(self) -> u32 {
IntegerType::try_from(self.inner)
.expect("integer_bit_width called on a non-integer type")
Expand Down
20 changes: 20 additions & 0 deletions solx-mlir/src/ir/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,26 @@ impl<'context> Value<'context> {
Self::constant_from_bigint(&BigInt::one(), result_type, context)
}

/// Materialises the smallest value the integer `result_type` admits: `type(T).min`.
pub fn minimum(result_type: Type<'context>, context: &Context<'context>) -> Self {
let value = if result_type.is_signed_integer() {
-(BigInt::one() << (result_type.integer_bit_width() - 1))
} else {
BigInt::zero()
};
Self::constant_from_bigint(&value, result_type, context)
}

/// Materialises the largest value the integer `result_type` admits: `type(T).max`.
pub fn maximum(result_type: Type<'context>, context: &Context<'context>) -> Self {
let value = if result_type.is_signed_integer() {
(BigInt::one() << (result_type.integer_bit_width() - 1)) - 1
} else {
(BigInt::one() << result_type.integer_bit_width()) - 1
};
Self::constant_from_bigint(&value, result_type, context)
}

/// Materialises an `i1` boolean constant.
pub fn boolean(value: bool, context: &Context<'context>) -> Self {
Self::constant(i64::from(value), Type::boolean(context.melior), context)
Expand Down
15 changes: 15 additions & 0 deletions solx-mlir/tests/lit/assignment_namespace_state_var.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// RUN: solx --emit-mlir=sol %s | FileCheck %s

// Assignment to a contract-qualified state variable (C.x = v): solc's
// print-init crashes (SIGSEGV), so this is solx-only.

// CHECK: sol.func @{{.*setNamespace.*}}
// CHECK: %[[V:.*]] = sol.load %{{[0-9]+}} : !sol.ptr<ui256, Stack>, ui256
// CHECK: %[[SLOT:.*]] = sol.addr_of @{{x.*}} : !sol.ptr<ui256, Storage>
// CHECK: sol.store %[[V]], %[[SLOT]] : ui256, !sol.ptr<ui256, Storage>

contract C {
uint256 x;

function setNamespace(uint256 v) public { C.x = v; }
}
26 changes: 26 additions & 0 deletions solx-mlir/tests/lit/error_event_selector.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// RUN: solx --emit-mlir=sol %s | FileCheck %s

// Error and event .selector are compile-time constants: solc's print-init
// crashes (SIGSEGV), so this is solx-only.

// CHECK: sol.func @{{.*error_selector.*}}
// CHECK: sol.constant 816952677 : ui32
// CHECK: sol.bytes_cast %{{.*}} : ui32 to !sol.fixedbytes<4>

// CHECK: sol.func @{{.*event_selector.*}}
// CHECK: sol.constant 48926247962583432353061649299097379571640188309989729032230735130183305912964 : ui256
// CHECK: sol.bytes_cast %{{.*}} : ui256 to !sol.fixedbytes<32>

error MyError(uint256 x);

contract C {
event MyEvent(uint256 indexed a);

function error_selector() external pure returns (bytes4) {
return MyError.selector;
}

function event_selector() external pure returns (bytes32) {
return MyEvent.selector;
}
}
74 changes: 74 additions & 0 deletions solx-mlir/tests/lit/external_getter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// RUN: solx --emit-mlir=sol %s | FileCheck %s
// RUN: solc --mlir-action=print-init %s 2>/dev/null | FileCheck %s

// CHECK: %[[T:.*]] = sol.this : !sol.contract<{{.*}}>
// CHECK: %[[A:.*]] = sol.address_cast %[[T]] : !sol.contract<{{.*}}> to !sol.address
// CHECK: sol.ext_call "{{.*}}"() at %[[A]] gas %{{.*}} value %{{.*}} selector %{{.*}} {callee_type = () -> ui256, static_call} : !sol.address, () -> (i1, ui256)

// CHECK: %[[T:.*]] = sol.this : !sol.contract<{{.*}}>
// CHECK: %[[A:.*]] = sol.address_cast %[[T]] : !sol.contract<{{.*}}> to !sol.address
// CHECK: sol.ext_call "{{.*}}"(%{{.*}}) at %[[A]] gas %{{.*}} value %{{.*}} selector %{{.*}} {callee_type = (ui256) -> ui256, static_call} : !sol.address, (ui256) -> (i1, ui256)

// CHECK: %[[T:.*]] = sol.this : !sol.contract<{{.*}}>
// CHECK: %[[A:.*]] = sol.address_cast %[[T]] : !sol.contract<{{.*}}> to !sol.address
// CHECK: sol.ext_call "{{.*}}"(%{{.*}}) at %[[A]] gas %{{.*}} value %{{.*}} selector %{{.*}} {callee_type = (ui256) -> ui256, static_call} : !sol.address, (ui256) -> (i1, ui256)

// CHECK: %[[T:.*]] = sol.this : !sol.contract<{{.*}}>
// CHECK: %[[A:.*]] = sol.address_cast %[[T]] : !sol.contract<{{.*}}> to !sol.address
// CHECK: %{{.*}}, %[[R:.*]]:2 = sol.ext_call "{{.*}}"() at %[[A]] gas %{{.*}} value %{{.*}} selector %{{.*}} {callee_type = () -> (ui256, ui256), static_call} : !sol.address, () -> (i1, ui256, ui256)
// CHECK: sol.return %[[R]]#0, %[[R]]#1 : ui256, ui256

// CHECK: sol.ext_call "{{.*}}"(%{{.*}}) at %{{.*}} gas %{{.*}} value %{{.*}} selector %{{.*}} {callee_type = (ui256) -> ui256, static_call} : !sol.address, (ui256) -> (i1, ui256)
// CHECK: sol.ext_call "{{.*}}"(%{{.*}}) at %{{.*}} gas %{{.*}} value %{{.*}} selector %{{.*}} {callee_type = (ui256) -> ui256, static_call} : !sol.address, (ui256) -> (i1, ui256)

contract Elementary {
uint256 public x;

function g() external returns (uint256) {
return this.x();
}
}

contract Mapping {
mapping(uint256 => uint256) public m;

function g(uint256 k) external returns (uint256) {
return this.m(k);
}
}

contract Sequence {
uint256[] public array;

function g(uint256 i) external returns (uint256) {
return this.array(i);
}
}

contract Struct {
struct S {
uint256 a;
uint256 b;
}

S public s;

function g() external returns (uint256, uint256) {
return this.s();
}
}

contract Token {
mapping(uint256 => uint256) public m;
uint256[] public array;
}

contract Viewer {
function readArray(Token o, uint256 index) external view returns (uint256) {
return o.array(index);
}

function readMapping(Token o, uint256 key) external view returns (uint256) {
return o.m(key);
}
}
73 changes: 73 additions & 0 deletions solx-mlir/tests/lit/fallback_receive.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// RUN: solx --emit-mlir=sol %s | FileCheck %s
// RUN: solc --mlir-action=print-init %s 2>/dev/null | FileCheck %s

// CHECK: sol.func @{{.*}} attributes {{.*}}kind = #{{.*}}Constructor

// CHECK: sol.func @{{.*}} attributes {{.*}}kind = #{{.*}}Receive, state_mutability = #{{.*}}Payable

// CHECK: sol.func @{{.*}} attributes {{.*}}kind = #{{.*}}Fallback{{.*}}state_mutability = #{{.*}}Payable

// CHECK: sol.func @{{.*}}() attributes {{.*}}kind = #Fallback{{.*}}state_mutability = #NonPayable
// CHECK: sol.get_calldata : !sol.string<CallData>
// CHECK: sol.length %{{.*}} : !sol.string<CallData>
// CHECK: sol.store %{{.*}}, %{{.*}} : ui256, !sol.ptr<ui256, Storage>

// CHECK: sol.func @{{.*}}() attributes {{.*}}kind = #Fallback{{.*}}state_mutability = #NonPayable
// CHECK: sol.sig : !sol.fixedbytes<4>
// CHECK: sol.store %{{.*}}, %{{.*}} : !sol.fixedbytes<4>, !sol.ptr<!sol.fixedbytes<4>, Storage>

// CHECK: sol.func @{{.*}}() attributes {{.*}}kind = #Fallback{{.*}}state_mutability = #Payable
// CHECK: %[[VALUE:.*]] = sol.callvalue : ui256
// CHECK: sol.store %[[VALUE]], %{{.*}} : ui256, !sol.ptr<ui256, Storage>

// CHECK: sol.func @{{.*}}() attributes {{.*}}kind = #Receive{{.*}}state_mutability = #Payable
// CHECK: %[[VALUE:.*]] = sol.callvalue : ui256
// CHECK: sol.store %[[VALUE]], %{{.*}} : ui256, !sol.ptr<ui256, Storage>

contract AllKinds {
uint256 x;

constructor(uint256 val) {
x = val;
}

receive() external payable {}

fallback() external payable {}

function get() public view returns (uint256) {
return x;
}
}

contract MsgDataLength {
uint256 public lastLength;

fallback() external {
lastLength = msg.data.length;
}
}

contract MsgSig {
bytes4 public lastSignature;

fallback() external {
lastSignature = msg.sig;
}
}

contract PayableFallback {
uint256 public received;

fallback() external payable {
received = msg.value;
}
}

contract PayableReceive {
uint256 public total;

receive() external payable {
total = msg.value;
}
}
Loading
Loading