diff --git a/mlir/include/mlir/Conversion/SolToStandard/EVMUtil.h b/mlir/include/mlir/Conversion/SolToStandard/EVMUtil.h index 49bcab906544..aa2b7b28d77a 100644 --- a/mlir/include/mlir/Conversion/SolToStandard/EVMUtil.h +++ b/mlir/include/mlir/Conversion/SolToStandard/EVMUtil.h @@ -58,18 +58,6 @@ unsigned getCallDataHeadSize(Type ty); /// the element type size. int64_t getMallocSize(Type ty); -/// MLIR version of solidity ast's Type::storageSize(). -unsigned getStorageSlotCount(Type ty); - -/// Returns true if the type can be packed within a storage slot. -/// Packable types (scalars) need {slot, offset} representation. -/// Non-packable types (arrays, structs, mappings) are slot-aligned and only -/// need slot. -bool canBePacked(mlir::Type ty); - -/// Returns the byte size of a packable type in storage. -unsigned getStorageByteSize(mlir::Type ty); - /// IR Builder for EVM specific lowering. class Builder { // It's possible to provide a mlirgen::BuilderHelper member with same default @@ -180,6 +168,12 @@ class Builder { sol::DataLocation dataLoc = sol::DataLocation::Storage, std::optional locArg = std::nullopt); + /// Cleans up a packed storage value to match Solidity storage-load semantics + /// for the given element type. + Value + genCleanupPackedStorageValue(Type eltTy, Value value, + std::optional locArg = std::nullopt); + /// Inserts integer value (<=32 bytes) to the slot value: /// or(and(slot, holeMask), shiftedVal), where /// holeMask = not(ones(numBits) << offset * 8), diff --git a/mlir/include/mlir/Dialect/Sol/Sol.h b/mlir/include/mlir/Dialect/Sol/Sol.h index a0a8a048a5f7..08935fdb112a 100644 --- a/mlir/include/mlir/Dialect/Sol/Sol.h +++ b/mlir/include/mlir/Dialect/Sol/Sol.h @@ -62,6 +62,18 @@ bool isRevertStringsEnabled(ModuleOp mod); /// Return true if the type is address-like (i.e. address or contract type). bool isAddressLikeType(Type ty); +/// MLIR version of solidity ast's Type::storageSize(). +unsigned getStorageSlotCount(Type ty); + +/// Returns true if the type can be packed within a storage slot. +/// Packable types (scalars) need {slot, offset} representation. +/// Non-packable types (arrays, structs, mappings) are slot-aligned and only +/// need slot. +bool canBePacked(mlir::Type ty); + +/// Returns the byte size of a packable type in storage. +unsigned getStorageByteSize(mlir::Type ty); + /// /// The following functions are used to query the capabilities of the specified /// evm in the module. diff --git a/mlir/include/mlir/Dialect/Sol/SolBase.td b/mlir/include/mlir/Dialect/Sol/SolBase.td index ea5f4c3dd41c..aed9acf865c7 100644 --- a/mlir/include/mlir/Dialect/Sol/SolBase.td +++ b/mlir/include/mlir/Dialect/Sol/SolBase.td @@ -104,7 +104,34 @@ def Sol_ArrayType : Sol_Type<"Array", "array"> { def Sol_StructType : Sol_Type<"Struct", "struct"> { let parameters = (ins ArrayRefParameter<"Type">:$memberTypes, - Sol_DataLocation:$dataLocation); + Sol_DataLocation:$dataLocation, + ArrayRefParameter<"uint64_t">:$memberSlotOffsets, + ArrayRefParameter<"uint64_t">:$memberByteOffsets, + "uint64_t":$storageSlotCount); + let skipDefaultBuilders = 1; + let builders = [ + TypeBuilder<(ins "ArrayRef":$memberTypes, + "DataLocation":$dataLocation), [{ + ::llvm::SmallVector memberSlotOffsets; + ::llvm::SmallVector memberByteOffsets; + uint64_t storageSlotCount = 0; + if (dataLocation == DataLocation::Storage) + computeStructStorageMemberOffsets(memberTypes, memberSlotOffsets, + memberByteOffsets, + storageSlotCount); + + return $_get($_ctxt, memberTypes, dataLocation, memberSlotOffsets, + memberByteOffsets, storageSlotCount); + }]> + ]; + let extraClassDeclaration = [{ + struct StorageMemberOffset { + uint64_t slotOffset; + uint64_t byteOffset; + }; + + StorageMemberOffset getStorageMemberOffset(uint64_t memberIdx) const; + }]; let hasCustomAssemblyFormat = 1; } diff --git a/mlir/lib/Conversion/SolToStandard/EVMUtil.cpp b/mlir/lib/Conversion/SolToStandard/EVMUtil.cpp index 7528b167a847..172eda11692a 100644 --- a/mlir/lib/Conversion/SolToStandard/EVMUtil.cpp +++ b/mlir/lib/Conversion/SolToStandard/EVMUtil.cpp @@ -24,6 +24,199 @@ using namespace mlir; +namespace { +// Reads struct members for ABI encoding from a concrete source location +// (calldata, memory, or storage). +struct StructEncodeMemberReader { + sol::StructType structTy; + evm::Builder &evmB; + OpBuilder &b; + Location loc; + mlir::solgen::BuilderExt bExt; + + StructEncodeMemberReader(sol::StructType structTy, evm::Builder &evmB, + OpBuilder &b, Location loc) + : structTy(structTy), evmB(evmB), b(b), loc(loc), bExt(b, loc) {} + + virtual ~StructEncodeMemberReader() = default; + + Type getMemberType(uint64_t memberIdx) const { + return structTy.getMemberTypes()[memberIdx]; + } + + // Returns the source value for the struct member at memberIdx. + virtual Value read(uint64_t memberIdx) = 0; + + // Emits instruction to advance source position for the next member, + // if needed. + virtual void advance(uint64_t memberIdx) = 0; +}; + +struct StructEncodeMemberReaderCallData final : StructEncodeMemberReader { + // Base address of this struct head in calldata. + Value baseAddr; + // Current calldata head cursor for member traversal. + Value curAddr; + + StructEncodeMemberReaderCallData(sol::StructType structTy, evm::Builder &evmB, + OpBuilder &b, Location loc, Value baseAddr) + : StructEncodeMemberReader(structTy, evmB, b, loc), baseAddr(baseAddr), + curAddr(baseAddr) {} + + Value read(uint64_t memberIdx) override { + Type memTy = getMemberType(memberIdx); + if (sol::hasDynamicallySizedElt(memTy)) { + auto genCalldataAccessLengthAndStrideGuards = [&](Value dataAddr, + Value length, + Value stride) { + // Check that the dynamic length fits uint64. + Value invalidLength = b.create( + loc, arith::CmpIPredicate::ugt, length, + bExt.genI256Const(APInt::getLowBitsSet(256, 64))); + evmB.genRevertWithMsg(invalidLength, "Invalid calldata access length", + loc); + + // Check that data start + length * stride stays within calldata. + Value callDataSize = b.create(loc); + Value maxDataAddr = b.create( + loc, callDataSize, b.create(loc, length, stride)); + Value invalidStride = b.create( + loc, arith::CmpIPredicate::sgt, dataAddr, maxDataAddr); + evmB.genRevertWithMsg(invalidStride, "Invalid calldata access stride", + loc); + }; + + // Dynamic calldata members are encoded as offsets wrt to the beginning + // of this struct head. + Value memberOffset = + evmB.genLoad(curAddr, sol::DataLocation::CallData, loc); + + // Check that the dynamic member head offset is within calldata bounds. + unsigned neededLength = evm::getCallDataHeadSize(memTy); + assert(neededLength > 0 && "Expected non-zero calldata head size"); + Value callDataSize = b.create(loc); + Value maxRelOffset = b.create( + loc, b.create(loc, callDataSize, baseAddr), + bExt.genI256Const(neededLength - 1)); + Value invalidOffset = b.create( + loc, arith::CmpIPredicate::sge, memberOffset, maxRelOffset); + evmB.genRevertWithMsg(invalidOffset, "Invalid calldata access offset", + loc); + Value memberAddr = b.create(loc, baseAddr, memberOffset); + + if (auto memArrTy = dyn_cast(memTy)) { + if (memArrTy.isDynSized()) { + // Dynamic calldata arrays are passed as (dataPtr, size) pair. + Value innerSize = b.create(loc, memberAddr); + Value innerDataAddr = + b.create(loc, memberAddr, bExt.genI256Const(32)); + genCalldataAccessLengthAndStrideGuards( + innerDataAddr, innerSize, + bExt.genI256Const( + evm::getCallDataHeadSize(memArrTy.getEltType()))); + return bExt.genLLVMStruct({innerDataAddr, innerSize}); + } + + // Static calldata arrays are forwarded by address. + return memberAddr; + } + + if (isa(memTy)) { + // Strings are dynamic and use the same (dataPtr, size) representation. + Value innerSize = b.create(loc, memberAddr); + Value innerDataAddr = + b.create(loc, memberAddr, bExt.genI256Const(32)); + genCalldataAccessLengthAndStrideGuards(innerDataAddr, innerSize, + bExt.genI256Const(1)); + return bExt.genLLVMStruct({innerDataAddr, innerSize}); + } + + // Nested structs in calldata are forwarded by their head address. + if (isa(memTy)) + return memberAddr; + + llvm_unreachable("Unexpected dynamically sized type in calldata struct"); + } + + if (sol::isNonPtrRefType(memTy)) { + // Static non-pointer refs are laid out inline in calldata head and + // should be passed by address. + return curAddr; + } + + return evmB.genLoad(curAddr, sol::DataLocation::CallData, loc); + } + + void advance(uint64_t memberIdx) override { + Type memTy = getMemberType(memberIdx); + curAddr = b.create( + loc, curAddr, bExt.genI256Const(evm::getCallDataHeadSize(memTy))); + } +}; + +struct StructEncodeMemberReaderMemory final : StructEncodeMemberReader { + // Current memory head cursor for member traversal. + Value curAddr; + + StructEncodeMemberReaderMemory(sol::StructType structTy, evm::Builder &evmB, + OpBuilder &b, Location loc, Value curAddr) + : StructEncodeMemberReader(structTy, evmB, b, loc), curAddr(curAddr) {} + + Value read(uint64_t) override { + Value srcVal = evmB.genLoad(curAddr, sol::DataLocation::Memory, loc); + return srcVal; + } + + void advance(uint64_t) override { + // In memory, each struct member head entry occupies one 32-byte slot. + curAddr = b.create(loc, curAddr, bExt.genI256Const(32)); + } +}; + +struct StructEncodeMemberReaderStorage final : StructEncodeMemberReader { + // Base storage slot for the struct. + Value baseAddr; + + // Cache the last loaded storage slot to avoid repeated sload for packed + // members that share the same slot. + std::optional previousSlotOffset; + Value previousSlotValue; + + StructEncodeMemberReaderStorage(sol::StructType structTy, evm::Builder &evmB, + OpBuilder &b, Location loc, Value baseAddr) + : StructEncodeMemberReader(structTy, evmB, b, loc), baseAddr(baseAddr) {} + + Value read(uint64_t memberIdx) override { + auto [slotOffset, byteOffset] = structTy.getStorageMemberOffset(memberIdx); + Type memTy = getMemberType(memberIdx); + if (sol::canBePacked(memTy)) { + // Packed members are extracted from the resolved slot and cleaned to the + // source type width. + if (!previousSlotOffset || *previousSlotOffset != slotOffset) { + Value memberSlot = b.create( + loc, baseAddr, bExt.genI256Const(slotOffset)); + previousSlotValue = + evmB.genLoad(memberSlot, sol::DataLocation::Storage, loc); + previousSlotOffset = slotOffset; + } + + Value shifted = previousSlotValue; + if (byteOffset != 0) + shifted = b.create(loc, previousSlotValue, + bExt.genI256Const(byteOffset * 8)); + + return evmB.genCleanupPackedStorageValue(memTy, shifted, loc); + } + + return b.create(loc, baseAddr, + bExt.genI256Const(slotOffset)); + } + + // Storage traversal is indexed by member number and precomputed offsets. + void advance(uint64_t) override {} +}; +} // namespace + unsigned evm::getAlignment(evm::AddrSpace addrSpace) { // FIXME: Confirm this! return addrSpace == evm::AddrSpace_Stack ? evm::ByteLen_Field @@ -44,6 +237,13 @@ unsigned evm::getCallDataHeadSize(Type ty) { if (auto arrTy = dyn_cast(ty)) return arrTy.getSize() * getCallDataHeadSize(arrTy.getEltType()); + if (auto structTy = dyn_cast(ty)) { + unsigned size = 0; + for (Type memTy : structTy.getMemberTypes()) + size += getCallDataHeadSize(memTy); + return size; + } + llvm_unreachable("NYI: Other types"); } @@ -66,70 +266,6 @@ int64_t evm::getMallocSize(Type ty) { return 32; } -unsigned evm::getStorageSlotCount(Type ty) { - if (isa(ty) || isa(ty) || - isa(ty) || isa(ty) || - isa(ty) || isa(ty) || - sol::isAddressLikeType(ty)) - return 1; - - if (auto arrTy = dyn_cast(ty)) { - // Dynamic arrays store only the head slot in-place. - if (arrTy.isDynSized()) - return 1; - return arrTy.getSize() * getStorageSlotCount(arrTy.getEltType()); - } - - if (auto structTy = dyn_cast(ty)) { - int64_t sum = 0; - for (Type memTy : structTy.getMemberTypes()) - sum += getStorageSlotCount(memTy); - return sum; - } - - llvm_unreachable("NYI: Other types"); -} - -bool evm::canBePacked(Type ty) { - // Scalars can be packed within a slot. - if (isa(ty) || isa(ty) || - isa(ty) || isa(ty) || - sol::isAddressLikeType(ty)) - return true; - - // Aggregates are slot-aligned and cannot be packed. - if (isa(ty) || isa(ty) || - isa(ty) || isa(ty)) - return false; - - llvm_unreachable("NYI"); -} - -unsigned evm::getStorageByteSize(Type ty) { - assert(canBePacked(ty) && "Only packable types have byte size"); - - if (auto intTy = dyn_cast(ty)) - // Bool occupies 1 byte in storage. - return intTy.getWidth() == 1 ? 1 : intTy.getWidth() / 8; - - if (auto bytesTy = dyn_cast(ty)) - return bytesTy.getSize(); - - // Enums can have at most 256 members, so always 1 byte. - if (isa(ty)) - return 1; - - // Address-like types are 20 bytes. - if (sol::isAddressLikeType(ty)) - return 20; - - // Internal function reference. - if (isa(ty)) - return 8; - - llvm_unreachable("NYI"); -} - Value evm::Builder::normalizeABIScalarForEncoding( Type ty, Value val, Location loc, std::optional srcDataLoc) { @@ -510,7 +646,7 @@ Value evm::Builder::genAddrAtIdx(Value baseAddr, Value idx, Type ty, if (dataLoc == sol::DataLocation::Storage) { Value stride; if (auto arrTy = dyn_cast(ty)) - stride = bExt.genI256Const(evm::getStorageSlotCount(arrTy.getEltType())); + stride = bExt.genI256Const(sol::getStorageSlotCount(arrTy.getEltType())); else if (isa(ty)) stride = bExt.genI256Const(1); Value scaledIdx = b.create(loc, idx, stride); @@ -546,7 +682,7 @@ Value evm::Builder::genPackedStorageAddr(Value baseSlot, Value idx, Type eltTy, mlir::solgen::BuilderExt bExt(b, loc); auto [slot, byteOffset] = genPackedStorageAddrPair( - b, baseSlot, idx, getStorageByteSize(eltTy), isDataLeftAligned, loc); + b, baseSlot, idx, sol::getStorageByteSize(eltTy), isDataLeftAligned, loc); return bExt.genLLVMStruct({slot, byteOffset}); } @@ -578,6 +714,42 @@ Value evm::Builder::genPunchHole(Value slot, Value shiftBits, unsigned numBits, return genPunchHoleInValue(b, slotVal, shiftBits, numBits, loc); } +Value evm::Builder::genCleanupPackedStorageValue( + Type eltTy, Value value, std::optional locArg) { + Location loc = locArg ? *locArg : defLoc; + mlir::solgen::BuilderExt bExt(b, loc); + + if (auto bytesTy = dyn_cast(eltTy)) { + unsigned numBits = bytesTy.getSize() * 8; + if (numBits == 256) + return value; + return b.create(loc, value, + bExt.genI256Const(256 - numBits, loc)); + } + + if (auto intTy = dyn_cast(eltTy)) + return bExt.genIntCastWithBoolCleanup(intTy.getWidth(), intTy.isSigned(), + value, loc, + /*maskBoolAsStorageByte=*/true); + + if (sol::isAddressLikeType(eltTy)) { + APInt mask = APInt::getLowBitsSet(256, 160); + return b.create(loc, value, bExt.genI256Const(mask)); + } + + if (isa(eltTy)) { + APInt mask = APInt::getLowBitsSet(256, 8); + return b.create(loc, value, bExt.genI256Const(mask)); + } + + if (isa(eltTy)) { + APInt mask = APInt::getLowBitsSet(256, 64); + return b.create(loc, value, bExt.genI256Const(mask)); + } + + llvm_unreachable("Unexpected type for cleanup of packed storage value"); +} + Value evm::Builder::genLoad(Value addr, sol::DataLocation dataLoc, std::optional locArg) { Location loc = locArg ? *locArg : defLoc; @@ -1399,6 +1571,69 @@ Value evm::Builder::genABITupleEncoding( return forOp.getResult(2); } + // Struct type + if (auto structTy = dyn_cast(ty)) { + // If the struct itself is emitted into tail, initialize the struct-local + // tail to just past its ABI head. + if (dstAddrInTail) { + unsigned structHeadSize = 0; + for (Type memTy : structTy.getMemberTypes()) + structHeadSize += getCallDataHeadSize(memTy); + tailAddr = b.create(loc, dstAddr, + bExt.genI256Const(structHeadSize)); + } + + auto dataLoc = structTy.getDataLocation(); + std::unique_ptr reader; + switch (dataLoc) { + case sol::DataLocation::CallData: + reader = std::make_unique( + structTy, *this, b, loc, src); + break; + case sol::DataLocation::Memory: + reader = std::make_unique(structTy, *this, + b, loc, src); + break; + case sol::DataLocation::Storage: + reader = std::make_unique( + structTy, *this, b, loc, src); + break; + default: + llvm_unreachable("Unexpected data location for struct encoding"); + } + + Value structHeadAddr = dstAddr; + auto memberTypes = structTy.getMemberTypes(); + for (uint64_t i = 0, e = memberTypes.size(); i < e; ++i) { + Type memTy = memberTypes[i]; + Value srcVal = reader->read(i); + + if (sol::hasDynamicallySizedElt(memTy)) { + // Dynamic members store a tail offset in the head, then encode payload + // at the current tail. + b.create( + loc, structHeadAddr, + b.create(loc, tailAddr, dstAddr)); + tailAddr = + genABITupleEncoding(memTy, srcVal, tailAddr, /*dstAddrInTail=*/true, + dstAddr, tailAddr, loc, dataLoc); + } else { + // Static members never advance the tail on their own. + tailAddr = genABITupleEncoding(memTy, srcVal, structHeadAddr, + /*dstAddrInTail=*/false, dstAddr, + tailAddr, loc, dataLoc); + } + + // Advance the addresses iff there are more members to encode. + if (i + 1 < e) { + structHeadAddr = b.create( + loc, structHeadAddr, bExt.genI256Const(getCallDataHeadSize(memTy))); + reader->advance(i); + } + } + return tailAddr; + } + // String type if (auto stringTy = dyn_cast(ty)) { auto tailDataAddr = @@ -1604,6 +1839,15 @@ Value evm::Builder::genABITupleDecoding(Type ty, Value addr, bool fromMem, genRevertWithMsg(invalidRangeCond, revertMsg, loc); }; + // Revert if offset is above uint64 max (0xffffffffffffffff). + auto genRevertIfOffsetTooLarge = [&](Value offset, + std::string const &revertMsg) { + auto invalidOffsetCond = b.create( + loc, arith::CmpIPredicate::ugt, offset, + bExt.genI256Const(APInt::getLowBitsSet(256, 64))); + genRevertWithMsg(invalidOffsetCond, revertMsg, loc); + }; + // Revert if a value is past tuple end. auto genRevertIfPastTupleEnd = [&](Value value, std::string const &revertMsg) { @@ -1713,12 +1957,8 @@ Value evm::Builder::genABITupleDecoding(Type ty, Value addr, bool fromMem, Value iSrcAddr = initArgs[1]; if (sol::hasDynamicallySizedElt(arrTy.getEltType())) { Value innerOffset = genLoad(iSrcAddr); - auto invalidInnerOffsetCond = b.create( - loc, arith::CmpIPredicate::ugt, innerOffset, - bExt.genI256Const(APInt::getLowBitsSet(256, 64))); - genRevertWithMsg(invalidInnerOffsetCond, - "ABI decoding: invalid calldata array offset", - loc); + genRevertIfOffsetTooLarge( + innerOffset, "ABI decoding: invalid calldata array offset"); // The elements are offset wrt to the start of this array (after the // size field if dynamic) that contain the inner element. @@ -1808,6 +2048,67 @@ Value evm::Builder::genABITupleDecoding(Type ty, Value addr, bool fromMem, return dstAddr; } + // Struct type + if (auto structTy = dyn_cast(ty)) { + auto dataLoc = structTy.getDataLocation(); + assert((dataLoc == sol::DataLocation::CallData || + dataLoc == sol::DataLocation::Memory) && + "Unexpected struct data location"); + + unsigned structHeadSize = 0; + for (Type memTy : structTy.getMemberTypes()) + structHeadSize += getCallDataHeadSize(memTy); + + std::string const &headTooShortMsg = + dataLoc == sol::DataLocation::CallData + ? "ABI decoding: struct calldata too short" + : "ABI decoding: struct data too short"; + Value srcEnd = + b.create(loc, addr, bExt.genI256Const(structHeadSize)); + genRevertIfPastTupleEnd(srcEnd, headTooShortMsg); + + // Keep calldata structs as calldata pointers. + if (dataLoc == sol::DataLocation::CallData) + return addr; + + Value dstAddr = genMemAlloc( + bExt.genI256Const(structTy.getMemberTypes().size() * 32), loc); + Value dstHeadAddr = dstAddr; + Value srcHeadAddr = addr; + for (Type memTy : structTy.getMemberTypes()) { + Value memberVal; + if (sol::hasDynamicallySizedElt(memTy)) { + Value tailOffset = genLoad(srcHeadAddr); + genRevertIfOffsetTooLarge(tailOffset, + "ABI decoding: invalid struct offset"); + + Value tailAddr = b.create(loc, addr, tailOffset); + memberVal = + genABITupleDecoding(memTy, tailAddr, fromMem, addr, tupleEnd, loc); + } else { + memberVal = genABITupleDecoding(memTy, srcHeadAddr, fromMem, addr, + tupleEnd, loc); + } + + // If the element type is an integer smaller than 256 bits, we need + // to extend it. In case we don't do that, store will place the + // value in the higher bits, which is incorrect. + auto intTy = dyn_cast(memberVal.getType()); + if (intTy && intTy.getWidth() < 256) + memberVal = + bExt.genIntCast(/*width=*/256, intTy.isSigned(), memberVal, loc); + + b.create(loc, dstHeadAddr, memberVal); + + srcHeadAddr = b.create( + loc, srcHeadAddr, bExt.genI256Const(getCallDataHeadSize(memTy))); + dstHeadAddr = + b.create(loc, dstHeadAddr, bExt.genI256Const(32)); + } + + return dstAddr; + } + llvm_unreachable("NYI"); } diff --git a/mlir/lib/Conversion/SolToStandard/SolToYul.cpp b/mlir/lib/Conversion/SolToStandard/SolToYul.cpp index 647aa42f795a..1b9b398124ca 100644 --- a/mlir/lib/Conversion/SolToStandard/SolToYul.cpp +++ b/mlir/lib/Conversion/SolToStandard/SolToYul.cpp @@ -1447,11 +1447,11 @@ struct PushOpLowering : public OpConversionPattern { llvm_unreachable(""); } - if (evm::canBePacked(eltTy)) { + if (sol::canBePacked(eltTy)) { r.replaceOp(op, evmB.genPackedStorageAddr(dataSlot, oldSize, eltTy)); } else { // Slot-aligned layout. - Value stride = bExt.genI256Const(evm::getStorageSlotCount(eltTy)); + Value stride = bExt.genI256Const(sol::getStorageSlotCount(eltTy)); Value scaledIdx = r.create(loc, oldSize, stride); r.replaceOp(op, r.create(loc, dataSlot, scaledIdx)); } @@ -1526,10 +1526,10 @@ struct PopOpLowering : public OpConversionPattern { } // Zero the deleted element. - if (evm::canBePacked(eltTy)) { + if (sol::canBePacked(eltTy)) { // Packed: load-modify-store to clear just those bytes. - unsigned numBits = evm::getStorageByteSize(eltTy) * 8; - Value eltByteSize = bExt.genI256Const(evm::getStorageByteSize(eltTy)); + unsigned numBits = sol::getStorageByteSize(eltTy) * 8; + Value eltByteSize = bExt.genI256Const(sol::getStorageByteSize(eltTy)); Value bytePos = r.create(loc, newSize, eltByteSize); Value thirtyTwo = bExt.genI256Const(32); Value slotOffset = r.create(loc, bytePos, thirtyTwo); @@ -1542,7 +1542,7 @@ struct PopOpLowering : public OpConversionPattern { r.create(loc, tailSlot, cleared); } else { // Slot-aligned: zero the whole slot. - Value stride = bExt.genI256Const(evm::getStorageSlotCount(eltTy)); + Value stride = bExt.genI256Const(sol::getStorageSlotCount(eltTy)); Value scaledIdx = r.create(loc, newSize, stride); Value tailAddr = r.create(loc, dataSlot, scaledIdx); r.create(loc, tailAddr, bExt.genI256Const(0)); @@ -1565,7 +1565,7 @@ struct AddrOfOpLowering : public OpRewritePattern { assert(sym); if (auto stateVarOp = dyn_cast(sym)) { Value slot = bExt.genI256Const(stateVarOp.getSlot()); - if (evm::canBePacked(stateVarOp.getType())) { + if (sol::canBePacked(stateVarOp.getType())) { Value offset = bExt.genI256Const(stateVarOp.getByteOffset()); r.replaceOp(op, bExt.genLLVMStruct({slot, offset})); } else { @@ -1632,11 +1632,11 @@ struct GepOpLowering : public OpConversionPattern { ? evmB.genDataAddrPtr(remappedBaseAddr, baseAddrTy) : remappedBaseAddr; - if (evm::canBePacked(eltTy)) { + if (sol::canBePacked(eltTy)) { res = evmB.genPackedStorageAddr(baseSlot, castedIdx, eltTy); } else { // Slot-aligned layout. - Value stride = bExt.genI256Const(evm::getStorageSlotCount(eltTy)); + Value stride = bExt.genI256Const(sol::getStorageSlotCount(eltTy)); Value scaledIdx = r.create(loc, castedIdx, stride); res = r.create(loc, baseSlot, scaledIdx); } @@ -1644,50 +1644,20 @@ struct GepOpLowering : public OpConversionPattern { } else if (auto structTy = dyn_cast(baseAddrTy)) { // Field index is always constant for struct access. auto constIdx = cast(idx.getDefiningOp()); - int64_t fieldIdx = constIdx.value(); + uint64_t fieldIdx = constIdx.value(); ArrayRef memberTypes = structTy.getMemberTypes(); Type fieldTy = memberTypes[fieldIdx]; - - // Compute slot/byte offset by walking preceding fields. - unsigned slotOffset = 0; - unsigned byteOffset = 0; - - for (int64_t i = 0; i < fieldIdx; ++i) { - Type prevTy = memberTypes[i]; - - if (evm::canBePacked(prevTy)) { - unsigned prevSize = evm::getStorageByteSize(prevTy); - if (byteOffset + prevSize > 32) { - slotOffset++; - byteOffset = 0; - } - byteOffset += prevSize; - } else { - // Non-packable: flush partial slot, add its slot count. - if (byteOffset > 0) { - slotOffset++; - byteOffset = 0; - } - slotOffset += evm::getStorageSlotCount(prevTy); - } - } + auto [slotOffset, byteOffset] = + structTy.getStorageMemberOffset(fieldIdx); // Position for target field. - if (evm::canBePacked(fieldTy)) { - unsigned fieldSize = evm::getStorageByteSize(fieldTy); - if (byteOffset + fieldSize > 32) { - slotOffset++; - byteOffset = 0; - } + if (sol::canBePacked(fieldTy)) { // Result: {baseSlot + slotOffset, byteOffset} Value slot = r.create(loc, remappedBaseAddr, bExt.genI256Const(slotOffset)); res = bExt.genLLVMStruct({slot, bExt.genI256Const(byteOffset)}); } else { - // Non-packable: flush partial slot. - if (byteOffset > 0) - slotOffset++; res = r.create(loc, remappedBaseAddr, bExt.genI256Const(slotOffset)); } @@ -1831,7 +1801,7 @@ struct MapOpLowering : public OpConversionPattern { Type resTy = op.getResult().getType(); if (auto ptrTy = dyn_cast(resTy); ptrTy && ptrTy.getDataLocation() == sol::DataLocation::Storage && - evm::canBePacked(ptrTy.getPointeeType())) { + sol::canBePacked(ptrTy.getPointeeType())) { r.replaceOp(op, bExt.genLLVMStruct({slot, zero})); } else { r.replaceOp(op, slot); @@ -1906,7 +1876,7 @@ struct LoadOpLowering : public OpConversionPattern { return r.create(loc, slot); }; - if (evm::canBePacked(eltTy)) { + if (sol::canBePacked(eltTy)) { // addr is {slot, offset} struct Value slot = r.create( loc, i256Ty, addr, r.getDenseI64ArrayAttr({0})); @@ -1918,37 +1888,8 @@ struct LoadOpLowering : public OpConversionPattern { Value shiftBits = r.create(loc, offset, bExt.genI256Const(8)); Value shifted = r.create(loc, slotVal, shiftBits); - - // BytesType: shl to restore high-bits alignment. - if (auto bytesTy = dyn_cast(eltTy)) { - unsigned numBits = bytesTy.getSize() * 8; - Value res = r.create(loc, shifted, - bExt.genI256Const(256 - numBits)); - r.replaceOp(op, res); - } else if (sol::isAddressLikeType(op.getType())) { - APInt mask = APInt::getLowBitsSet(256, 160); - r.replaceOpWithNewOp(op, shifted, - bExt.genI256Const(mask)); - } else if (auto intTy = dyn_cast(op.getType())) { - Value castedRes = bExt.genIntCastWithBoolCleanup( - intTy.getWidth(), intTy.isSigned(), shifted, loc, - /*maskBoolAsStorageByte=*/true); - r.replaceOp(op, castedRes); - } else if (isa(eltTy)) { - // FuncRef is 64 bits, mask to lower 64 bits. - APInt mask = APInt::getLowBitsSet(256, 64); - Value masked = - r.create(loc, shifted, bExt.genI256Const(mask)); - r.replaceOp(op, masked); - } else if (isa(eltTy)) { - // Enums are 1 byte, mask to lower 8 bits. - APInt mask = APInt::getLowBitsSet(256, 8); - Value masked = - r.create(loc, shifted, bExt.genI256Const(mask)); - r.replaceOp(op, masked); - } else { - llvm_unreachable("NYI"); - } + Value cleaned = evmB.genCleanupPackedStorageValue(eltTy, shifted, loc); + r.replaceOp(op, cleaned); } else { // addr is just slot Value slotVal = genSlotLoad(addr); @@ -2055,7 +1996,7 @@ struct StoreOpLowering : public OpConversionPattern { r.create(loc, slot, val); }; - if (evm::canBePacked(eltTy)) { + if (sol::canBePacked(eltTy)) { // addr is {slot, offset} struct Value slot = r.create( loc, i256Ty, remappedAddr, r.getDenseI64ArrayAttr({0})); diff --git a/mlir/lib/Conversion/SolToStandard/TypeConverter.cpp b/mlir/lib/Conversion/SolToStandard/TypeConverter.cpp index 8b62b00b11ec..d90ef1e3264c 100644 --- a/mlir/lib/Conversion/SolToStandard/TypeConverter.cpp +++ b/mlir/lib/Conversion/SolToStandard/TypeConverter.cpp @@ -178,7 +178,7 @@ evm::SolTypeConverter::SolTypeConverter() { // otherwise. case sol::DataLocation::Storage: case sol::DataLocation::Transient: - if (evm::canBePacked(ty.getPointeeType())) + if (sol::canBePacked(ty.getPointeeType())) return LLVM::LLVMStructType::getLiteral(ty.getContext(), {i256Ty, i256Ty}); return i256Ty; diff --git a/mlir/lib/Dialect/Sol/SolBase.cpp b/mlir/lib/Dialect/Sol/SolBase.cpp index 3724b994c392..07a261e3155b 100644 --- a/mlir/lib/Dialect/Sol/SolBase.cpp +++ b/mlir/lib/Dialect/Sol/SolBase.cpp @@ -190,6 +190,74 @@ bool mlir::sol::isAddressLikeType(Type ty) { return isa(ty); } +unsigned mlir::sol::getStorageSlotCount(Type ty) { + if (isa(ty) || isa(ty) || isa(ty) || + isa(ty) || isa(ty) || isa(ty) || + isAddressLikeType(ty)) + return 1; + + if (auto arrTy = dyn_cast(ty)) { + // Dynamic arrays store only the head slot in-place. + if (arrTy.isDynSized()) + return 1; + + Type eltTy = arrTy.getEltType(); + unsigned size = arrTy.getSize(); + if (!canBePacked(eltTy)) + return size * getStorageSlotCount(eltTy); + + // Packed arrays of small elements can fit in fewer slots. + return llvm::divideCeil(size, 32u / getStorageByteSize(eltTy)); + } + + if (auto structTy = dyn_cast(ty)) { + assert(structTy.getDataLocation() == DataLocation::Storage && + "Storage slot count is only defined for storage structs"); + return structTy.getStorageSlotCount(); + } + + llvm_unreachable("NYI: Other types"); +} + +bool mlir::sol::canBePacked(Type ty) { + // Scalars can be packed within a slot. + if (isa(ty) || isa(ty) || isa(ty) || + isa(ty) || isAddressLikeType(ty)) + return true; + + // Aggregates are slot-aligned and cannot be packed. + if (isa(ty) || isa(ty) || isa(ty) || + isa(ty)) + return false; + + llvm_unreachable("NYI"); +} + +unsigned mlir::sol::getStorageByteSize(Type ty) { + assert(canBePacked(ty) && "Only packable types have byte size"); + + if (auto intTy = dyn_cast(ty)) + // Bool occupies 1 byte in storage. + return intTy.getWidth() == 1 ? 1 : intTy.getWidth() / 8; + + if (auto bytesTy = dyn_cast(ty)) + return bytesTy.getSize(); + + // Enums can have at most 256 members, so always 1 byte. + if (isa(ty)) + return 1; + + // Address-like types are 20 bytes. + if (isAddressLikeType(ty)) + return 20; + + // Internal function reference. + if (isa(ty)) + return 8; + + llvm_unreachable("NYI"); +} + static ParseResult parseDataLocation(AsmParser &parser, DataLocation &dataLocation) { StringRef dataLocationTok; @@ -333,6 +401,54 @@ void StructType::print(AsmPrinter &printer) const { printer << "), " << stringifyDataLocation(getDataLocation()) << ">"; } +static void computeStructStorageMemberOffsets( + ArrayRef memberTypes, SmallVectorImpl &slotOffsets, + SmallVectorImpl &byteOffsets, uint64_t &storageSlotCount) { + uint64_t slotOffset = 0; + uint64_t byteOffset = 0; + + slotOffsets.reserve(memberTypes.size()); + byteOffsets.reserve(memberTypes.size()); + + for (Type memberTy : memberTypes) { + if (canBePacked(memberTy)) { + uint64_t memberByteSize = getStorageByteSize(memberTy); + if (byteOffset + memberByteSize > 32) { + ++slotOffset; + byteOffset = 0; + } + + slotOffsets.push_back(slotOffset); + byteOffsets.push_back(byteOffset); + byteOffset += memberByteSize; + continue; + } + + if (byteOffset != 0) { + ++slotOffset; + byteOffset = 0; + } + slotOffsets.push_back(slotOffset); + byteOffsets.push_back(0); + slotOffset += getStorageSlotCount(memberTy); + } + + if (byteOffset > 0) + ++slotOffset; + + storageSlotCount = slotOffset; +} + +StructType::StorageMemberOffset +StructType::getStorageMemberOffset(uint64_t memberIdx) const { + assert(getDataLocation() == DataLocation::Storage && + "Storage offsets are only defined for storage structs"); + assert(memberIdx < getMemberTypes().size() && "Member index out of bounds"); + + return {/*slotOffset=*/getMemberSlotOffsets()[memberIdx], + /*byteOffset=*/getMemberByteOffsets()[memberIdx]}; +} + //===----------------------------------------------------------------------===// // PointerType //===----------------------------------------------------------------------===//