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

Hybrid memory representation #400

Draft
wants to merge 42 commits into
base: dev10
Choose a base branch
from

Conversation

danblitzhou
Copy link
Collaborator

  • added OpSemMemHybridRepr: store: array theory, load: ITE
  • iterative rewrite function for store Expr during loadAlignedWordFromMem for hybrid repr

Copy link
Collaborator

@priyasiddharth priyasiddharth left a comment

Choose a reason for hiding this comment

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

Left some comments mainly for my understanding...

/**
* rewriter for store expr:
* e = store A idx val -> { return ite( (idx == ptr), val, rewrite(A) )}
* e is not store (const array or terminal register) ->
Copy link
Collaborator

Choose a reason for hiding this comment

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

IIUC changing store to ite will allow us to hit the SAT solver rather than the SMT solver which knows about arrays.

Expr doPtrEq(Expr p1, Expr p2) { return m_alu.doEq(p1, p2, m_ptrSz); }

Expr operator()(Expr e) {
if (isOp<STORE>(e)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

will adding an assert that we expect a struct with three fields help with debugging?

LOG("opsem-hybrid", INFO << "From ptr " << *mem.toExpr() << "\n");

/** rewrite store into ITE **/
ArrayStoreRewriter rw(ptr.toExpr(), m_ctx.alu(),
Copy link
Collaborator

Choose a reason for hiding this comment

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

IIUC when we load we lazily rewrite the store instructions in the memory array rather than rewriting everytime we store... is this correct?

Copy link
Contributor

Choose a reason for hiding this comment

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

yes. optimizing while doing so. next layer is to optimize store instructions to simplify load optimizations later on

Expr rewriteITEComp(Expr exp);

struct RewriteFrame {
Expr m_exp;
Copy link
Contributor

Choose a reason for hiding this comment

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

comment the role of the fields

/* apply rewrite rules */
protected:
ExprFactory &m_efac; // for making expr
EZ3 &m_zctx; // for z3 simplifier if needed
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't see why generic rewriter should depend on Z3. Its specialization can, but the generic rewriter should not

protected:
Config m_config;
ExprFactory &m_efac; // for making expr
EZ3 &m_zctx; // for z3 simplifier if needed
Copy link
Contributor

Choose a reason for hiding this comment

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

should not be part of generic rewriter

in any of the above case, push e to top of result stack
otherwise return false and push e into top of rewriteStack
*/
bool visited(Expr e, size_t depth) {
Copy link
Contributor

Choose a reason for hiding this comment

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

the function has too many side-effects. The name does not reflect them all, neither does the comment.

Copy link
Contributor

Choose a reason for hiding this comment

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

perhaps renaming the function into visit would go long way to address the comment above

include/seahorn/Expr/ExprRewriter.hh Show resolved Hide resolved
rewrite_result rwRes = m_config.applyRewriteRules(new_exp);
if (rwRes.status == rewrite_status::RW_DONE) {
m_resultStack.push_back(rwRes.rewritten);
if (exp->use_count() > 1) {
Copy link
Contributor

Choose a reason for hiding this comment

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

replace exp->use_count() > 1 with shouldCache function for readability. Here and elsewhere

m_resultStack.resize(begin);
// apply rewrite rules to expression with new kids
rewrite_result rwRes = m_config.applyRewriteRules(new_exp);
if (rwRes.status == rewrite_status::RW_DONE) {
Copy link
Contributor

Choose a reason for hiding this comment

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

there has to be a way for a rule to indicate that no rewrite has happened. In this case, the result should not be cached.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

perhaps add a new status RW_SKIPPED

Copy link
Contributor

Choose a reason for hiding this comment

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

RW_SKIP

/* Pair <mem, ptr> */
using DagVisitMemCache = std::unordered_map<ENode *, ExprPair>;

inline Expr findInDagVisitCache(DagVisitCache &cache, Expr expr) {
Copy link
Contributor

Choose a reason for hiding this comment

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

what is this for? Document

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

not sure if this is needed anymore

}
}
// ite(true, a, b) => a
if (isOpX<TRUE>(i)) {
return {t, rewrite_status::RW_1};
Copy link
Contributor

Choose a reason for hiding this comment

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

why RW_1 ? a is already rewritten

}
// ite(false, a, b) => b
if (isOpX<FALSE>(i)) {
return {e, rewrite_status::RW_1};
Copy link
Contributor

Choose a reason for hiding this comment

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

RW_DONE

@@ -151,7 +152,45 @@ struct BoolOpRewriteRule : public ExprRewriteRule {
if (isOpX<NEG>(exp)) {
Expr neg = exp->arg(0);
if (isOpX<NEG>(neg)) {
return {neg->arg(0), rewrite_status::RW_2};
return {neg->arg(0), rewrite_status::RW_1};
Copy link
Contributor

Choose a reason for hiding this comment

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

RW_DONE

Expr e = arr->arg(2);
Expr new_t = op::array::select(t, idx);
Expr new_e = op::array::select(e, idx);
return {mk<ITE>(i, new_t, new_e), rewrite_status::RW_2};
Copy link
Contributor

Choose a reason for hiding this comment

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

overall, this is potentially very expensive in our application. It will create a lot of intermediate expressions while select is being pushed down. It would be more effective to implement push_select_over_ite method that pushes select to the bottom of the ite tree in one operation.

/** Read-over-write: select(store(arr_w, idx_w, val), idx_r)
==> ite(idx_w == idx_x, val, select(arr_w, idx_r))
**/
if (isOpX<STORE>(arr)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

same comment as above. Need a single operation that pushes select down a chain of stores, and maybe a combination that pushes select through the chain of ite and store

}

AddrRange zeroBitsRange(AddrRange &r, size_t bits) {
unsigned new_low = r.low >> bits;
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should check or assume r.isTop == false at first.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

good catch so that we can skip some operations

}
} // namespace utils

Expr rewriteHybridLoadMemExpr(Expr loadMem, Expr ptr, AddrRangeMap &arm) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I am confused about parameters you passed for this method. Why you add ptr but not use it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

good catch

ITECompRewriteConfig(ExprFactory &efac, DagVisitCache &cache,
AddrRangeMap &arm)
: m_iteRule(efac, cache), m_compRule(efac, cache),
m_boolRule(efac, cache), m_arrayRule(efac, cache, arm),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Looks like you only use ARM for select only. Maybe I did not fully get the usage of the ARM. Can Arm be used for other pointer expressions?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

we can perhaps use it during the initial rewrite from stores to ITEs

(to sqaush): use rewriter to apply pattern recursively

feat(ExprVisitor): set selected kids to visit instead of always visit all

(tsquash): use more restrained vistor to only visit first arg of store

(tsquash): rename, cleanup

use simple custom rewriter for array store; revert m_selectedKids
in house version is gud

feat(mem-repr): iterative rewriter for ITE expr
(intrim): cached visit; stateless rules

(intrim): OOP design
@agurfinkel
Copy link
Contributor

@danblitzhou can we close this PR?

@danblitzhou
Copy link
Collaborator Author

danblitzhou commented Dec 9, 2022 via email

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

Successfully merging this pull request may close these issues.

None yet

4 participants