Skip to content

Commit

Permalink
SignatureRefining: Notice LUB requirements of intrinsic calls (#6122)
Browse files Browse the repository at this point in the history
call.without.effects implies a call to the function reference in the last parameter,
so the values sent in the other parameters must be taken into account when
computing LUBs for refining arguments, otherwise we might refine so much that
the intrinsic call no longer validates.
  • Loading branch information
kripken authored Nov 15, 2023
1 parent 001be44 commit 20fe882
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 0 deletions.
26 changes: 26 additions & 0 deletions src/passes/SignatureRefining.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ struct SignatureRefining : public Pass {
std::vector<Call*> calls;
std::vector<CallRef*> callRefs;

// Additional calls to take into account. We store intrinsic calls here,
// as they must appear twice: call.without.effects is both a normal call
// and also takes a final parameter that is a function reference that is
// called, and so two signatures are relevant for it. For the latter, we
// add the call as an "extra call" (which is an unusual call, as it has an
// extra parameter at the end, the function reference, compared to what we
// expect for the signature being called).
std::vector<Call*> extraCalls;

// A possibly improved LUB for the results.
LUBFinder resultsLUB;

Expand Down Expand Up @@ -107,6 +116,17 @@ struct SignatureRefining : public Pass {
// called.
for (auto* call : info.calls) {
allInfo[module->getFunction(call->target)->type].calls.push_back(call);

// For call.without.effects, we also add the effective function being
// called as well. The final operand is the function reference being
// called, which defines that type.
if (Intrinsics(*module).isCallWithoutEffects(call)) {
auto targetType = call->operands.back()->type;
if (!targetType.isRef()) {
continue;
}
allInfo[targetType.getHeapType()].extraCalls.push_back(call);
}
}

// For indirect calls, add each call_ref to the type the call_ref uses.
Expand Down Expand Up @@ -187,6 +207,12 @@ struct SignatureRefining : public Pass {
for (auto* callRef : info.callRefs) {
updateLUBs(callRef->operands);
}
for (auto* call : info.extraCalls) {
// Note that these intrinsic calls have an extra function reference
// param at the end, but updateLUBs looks at |numParams| only, so it
// considers just the relevant parameters.
updateLUBs(call->operands);
}

// Find the final LUBs, and see if we found an improvement.
std::vector<Type> newParamsTypes;
Expand Down
57 changes: 57 additions & 0 deletions test/lit/passes/signature-refining.wast
Original file line number Diff line number Diff line change
Expand Up @@ -985,3 +985,60 @@
(struct.new $C) ;; this will allow this function's result to be refined to $C
)
)

;; Test we consider call.without.effects when deciding what to refine. $A has
;; two subtypes, B1 and B2, and a call.without.effects sends in one while a
;; normal call sends in the other. As a result, we cannot refine to either.
(module
(rec
;; CHECK: (rec
;; CHECK-NEXT: (type $A (sub (struct )))
(type $A (sub (struct)))

;; CHECK: (type $B1 (sub $A (struct )))
(type $B1 (sub $A (struct)))

;; CHECK: (type $B2 (sub $A (struct )))
(type $B2 (sub $A (struct)))
)

;; CHECK: (type $3 (func (param (ref $A) funcref)))

;; CHECK: (type $4 (func))

;; CHECK: (type $5 (func (param (ref $A))))

;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $no.side.effects (type $3) (param (ref $A) funcref)))
(import "binaryen-intrinsics" "call.without.effects" (func $no.side.effects
(param (ref $A))
(param funcref)
))

;; CHECK: (elem declare func $target)

;; CHECK: (func $calls (type $4)
;; CHECK-NEXT: (call $no.side.effects
;; CHECK-NEXT: (struct.new_default $B1)
;; CHECK-NEXT: (ref.func $target)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call $target
;; CHECK-NEXT: (struct.new_default $B2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $calls
(call $no.side.effects
(struct.new $B1)
(ref.func $target)
)
(call $target
(struct.new $B2)
)
)

;; CHECK: (func $target (type $5) (param $x (ref $A))
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
(func $target (param $x (ref $A))
;; Because of the two calls above, this cannot be refined.
)
)

0 comments on commit 20fe882

Please sign in to comment.