blob: 79a9973b530cef0ab67bc380e435bbfb6f023907 [file] [log] [blame] [edit]
/*
* Copyright 2022 WebAssembly Community Group participants
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef wasm_ir_possible_constant_h
#define wasm_ir_possible_constant_h
#include <variant>
#include "ir/properties.h"
#include "wasm-builder.h"
#include "wasm.h"
namespace wasm {
// Represents data about what constant values are possible in a particular
// place. There may be no values, or one, or many, or if a non-constant value is
// possible, then all we can say is that the value is "unknown" - it can be
// anything. The values can either be literal values (Literal) or the names of
// immutable globals (Name).
//
// Currently this just looks for a single constant value, and even two constant
// values are treated as unknown. It may be worth optimizing more than that TODO
struct PossibleConstantValues {
private:
// No possible value.
struct None : public std::monostate {};
// Many possible values, and so this represents unknown data: we cannot infer
// anything there.
struct Many : public std::monostate {};
using Variant = std::variant<None, Literal, Name, Many>;
Variant value;
public:
PossibleConstantValues() : value(None()) {}
bool operator==(const PossibleConstantValues& other) const {
return value == other.value;
}
// Notes the contents of an expression and update our internal knowledge based
// on it and all previous values noted.
void note(Expression* expr, Module& wasm) {
// If this is a constant literal value, note that.
if (Properties::isConstantExpression(expr)) {
note(Properties::getLiteral(expr));
return;
}
// If this is an immutable global that we get, note that.
if (auto* get = expr->dynCast<GlobalGet>()) {
auto* global = wasm.getGlobal(get->name);
if (global->mutable_ == Immutable) {
note(get->name);
return;
}
}
// Otherwise, this is not something we can reason about.
noteUnknown();
}
// Note either a Literal or a Name.
template<typename T> void note(T curr) {
PossibleConstantValues other;
other.value = curr;
combine(other);
}
// Notes a value that is unknown - it can be anything. We have failed to
// identify a constant value here.
void noteUnknown() { value = Many(); }
// Combine the information in a given PossibleConstantValues to this one. This
// is the same as if we have called note*() on us with all the history of
// calls to that other object.
//
// Returns whether we changed anything.
bool combine(const PossibleConstantValues& other) {
if (std::get_if<None>(&other.value)) {
return false;
}
if (std::get_if<None>(&value)) {
value = other.value;
return true;
}
if (std::get_if<Many>(&value)) {
return false;
}
if (other.value != value) {
value = Many();
return true;
}
// Nulls compare equal, and we could consider any of the input nulls as the
// combination of the two (as any of them would be valid to place in the
// location we are working to optimize). In order to have simple symmetric
// behavior here, which does not depend on the order of the inputs, use the
// LUB.
if (isNull() && other.isNull()) {
auto type = getConstantLiteral().type.getHeapType();
auto otherType = other.getConstantLiteral().type.getHeapType();
auto lub = HeapType::getLeastUpperBound(type, otherType);
if (!lub) {
// TODO: Remove this workaround once we have bottom types to assign to
// null literals.
value = Many();
return true;
}
if (*lub != type) {
value = Literal::makeNull(*lub);
return true;
}
return false;
}
return false;
}
// Check if all the values are identical and constant.
bool isConstant() const {
return !std::get_if<None>(&value) && !std::get_if<Many>(&value);
}
bool isConstantLiteral() const { return std::get_if<Literal>(&value); }
bool isConstantGlobal() const { return std::get_if<Name>(&value); }
bool isNull() const {
return isConstantLiteral() && getConstantLiteral().isNull();
}
// Returns the single constant value.
Literal getConstantLiteral() const {
assert(isConstant());
return std::get<Literal>(value);
}
Name getConstantGlobal() const {
assert(isConstant());
return std::get<Name>(value);
}
// Assuming we have a single value, make an expression containing that value.
Expression* makeExpression(Module& wasm) const {
Builder builder(wasm);
if (isConstantLiteral()) {
return builder.makeConstantExpression(getConstantLiteral());
} else {
auto name = getConstantGlobal();
return builder.makeGlobalGet(name, wasm.getGlobal(name)->type);
}
}
// Returns whether we have ever noted a value.
bool hasNoted() const { return !std::get_if<None>(&value); }
void dump(std::ostream& o) const {
o << '[';
if (!hasNoted()) {
o << "unwritten";
} else if (!isConstant()) {
o << "unknown";
} else if (isConstantLiteral()) {
o << getConstantLiteral();
} else if (isConstantGlobal()) {
o << '$' << getConstantGlobal();
}
o << ']';
}
};
} // namespace wasm
#endif // wasm_ir_possible_constant_h