Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MooreToCore] Properly deal with OOB access in dyn_extract #8201

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

maerhart
Copy link
Member

@maerhart maerhart commented Feb 7, 2025

The array slice case is not fixed yet. I couldn't really find a part in the SV standard about signed/unsigned indexing. I assume always interpreting the index operand as unsigned is fine.

The array slice case is not fixed yet. I couldn't really find a part in the SV standard about signed/unsigned indexing. I assume always interpreting the index operand as unsigned is fine.
rewriter.replaceOpWithNewOp<hw::ArraySliceOp>(op, resultType,
adaptor.getInput(), idx);
return success();
}

rewriter.replaceOpWithNewOp<hw::ArrayGetOp>(op, adaptor.getInput(), idx);
Value size = rewriter.create<hw::ConstantOp>(
op.getLoc(), adaptor.getLowBit().getType(), arrType.getNumElements());
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That can cause problems if the lowBit bitwidth is too small to hold the array size.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch. In that case we might be guaranteed to always access in bounds?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes 👍
But I need to make sure this IR is not built in that case because the pass would crash.

Copy link
Contributor

@fabianschuiki fabianschuiki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

rewriter.replaceOpWithNewOp<hw::ArraySliceOp>(op, resultType,
adaptor.getInput(), idx);
return success();
}

rewriter.replaceOpWithNewOp<hw::ArrayGetOp>(op, adaptor.getInput(), idx);
Value size = rewriter.create<hw::ConstantOp>(
op.getLoc(), adaptor.getLowBit().getType(), arrType.getNumElements());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch. In that case we might be guaranteed to always access in bounds?

Comment on lines +841 to +851
Value access =
rewriter.create<hw::ArrayGetOp>(op.getLoc(), adaptor.getInput(), idx);
int64_t bw = hw::getBitWidth(arrType.getElementType());
if (bw < 0)
return failure();

Value zeroValueRaw =
rewriter.create<hw::ConstantOp>(op.getLoc(), APInt(bw, 0));
Value zeroValue = rewriter.createOrFold<hw::BitcastOp>(
op.getLoc(), arrType.getElementType(), zeroValueRaw);
rewriter.replaceOpWithNewOp<comb::MuxOp>(op, isOOB, zeroValue, access);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks reasonable assuming that hw::ArrayGetOp has undefined behaviour (or undefined result) for out-of-bounds accesses. I don't remember off the top of my head whether out-of-bounds index in ArrayGetOp causes a potential segfault in the lowered LLVM IR… But it might be nice if hw::ArrayGetOp just produce an undefined result, but don't cause arbitrary undefined behaviour such as crashing the simulator. Then we can just do exactly what you did here: read the value and then discard it with a mux if it's out of bounds.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation of the HW aggregate ops is generally a bit sparse, but I'd say a OOB hw.array_get produces an undefined result, but no immediate UB. The HWToLLVM lowering currently does not take this into account. I'll create an issue for that.

@hailongSun2000
Copy link
Member

I have a question about the unsigned index, for example:

logic [6:-1] arr;
arr[-1];

Maybe -1 represents the max if we assume the index is unsigned, right 🤔?

@fabianschuiki
Copy link
Contributor

@hailongSun2000 I think in your example ImportVerilog would lower this to an access index 0, because -1 is the "address" of the least significant bit. During conversion, the accessed index gets remapped from the range [a:b] to [a-b:0]. 🙂

@hailongSun2000
Copy link
Member

@hailongSun2000 I think in your example ImportVerilog would lower this to an access index 0, because -1 is the "address" of the least significant bit. During conversion, the accessed index gets remapped from the range [a:b] to [a-b:0]. 🙂

That sounds great! All bit rang are converted into little endian, and LSB is always 0. Could you tell me where the implementation is? I'm interested to it. Thanks in advance! 😃

Copy link
Member

@hailongSun2000 hailongSun2000 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! 🥳

@fabianschuiki
Copy link
Contributor

@hailongSun2000 All bit rang are converted into little endian, and LSB is always 0. Could you tell me where the implementation is?

I think @maerhart added this getSelectIndex call here to deal with the index remapping in a PR a few days ago: 🙂

getSelectIndex(lowBit, range));

@maerhart
Copy link
Member Author

As a little side-note: the example above would be lowered to an moore.extract, not a moore.dyn_extract

Even though all ranges are converted to little endian starting at 0, the index could still be a negative signed value in SV.

bit [31:0] arr;
bit [31:0] idx = -1;
arr[$signed(idx)];

What index does this actually access? -1 or 4,294,967,295? I couldn't really find an answer to this question in the standard, so let me know if you know more 😄
If both are out-of-bounds, it doesn't really matter, but could you have something like

bit [4294967295:0] arr; // let's say all bits are 1
bit [31:0] idx = -1;
arr[$signed(idx)];

Should this now return 0 or 1? Does SV even support such big arrays (if it doesn't the same still applies but with smaller integers)?

@fabianschuiki
Copy link
Contributor

Since SV allows you to have negative indices on array declarations, like bit [3:-8] x, I'd assume that it supports signed indices when accessing arrays. So x[-2] would actually be treated as accessing index -2, which then gets translated to actual bit positions through that index remapping.

Since this only involves addition/subtraction which are identical in signed/unsigned arithmetic, I think we can just ignore the issue altogether: if you subtract the lower index of the declaration (-8) from the accessed index (-2), you'll automatically remap all valid negative indices to positive bit positions. Anything below the lowest bit address would overflow and be out of bounds immediately.

If I recall correctly, array index expressions have a self-determined type, not a type inferred from context. That would suggest in your example that the index $signed(idx) would still be a 32 bit number, even though this would create an ambiguity when accessing that large array. We could think about sign-extending signed indices in bit/part-select operations by one bit, to remove any ambiguity, or to sign/zero-extend the index to the minimum required to unambiguously address the entire array.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants