blob: f69927ecc10cfdcdc533129a9f454c7b29b74f1e [file] [log] [blame] [edit]
/*
* Copyright 2024 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.
*/
#include "ir/metadata.h"
#include "wasm-traversal.h"
#include "wasm.h"
namespace wasm::metadata {
namespace {
// List out instructions serially, so we can match them between the old and
// new copies.
//
// This is not that efficient, and in theory we could copy this in the
// caller context as the code is copied. However, we assume that most
// functions have no metadata, so this is faster in that common case.
struct Serializer
: public PostWalker<Serializer, UnifiedExpressionVisitor<Serializer>> {
Serializer(Expression* expr) { walk(expr); }
std::vector<Expression*> list;
void visitExpression(Expression* curr) { list.push_back(curr); }
};
} // anonymous namespace
void copyBetweenFunctions(Expression* origin,
Expression* copy,
Function* originFunc,
Function* copyFunc) {
if (originFunc->debugLocations.empty() &&
originFunc->codeAnnotations.empty()) {
// Nothing to copy.
return;
}
Serializer originList(origin);
Serializer copyList(copy);
auto& originDebug = originFunc->debugLocations;
auto& copyDebug = copyFunc->debugLocations;
auto& originAnnotations = originFunc->codeAnnotations;
auto& copyAnnotations = copyFunc->codeAnnotations;
assert(originList.list.size() == copyList.list.size());
for (Index i = 0; i < originList.list.size(); i++) {
{
auto iter = originDebug.find(originList.list[i]);
if (iter != originDebug.end()) {
copyDebug[copyList.list[i]] = iter->second;
}
}
{
auto iter = originAnnotations.find(originList.list[i]);
if (iter != originAnnotations.end()) {
copyAnnotations[copyList.list[i]] = iter->second;
}
}
}
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
// Given two expressions to use as keys, see if they have identical values (or
// are both absent) in two maps.
template<typename T, typename V>
bool compare(Expression* a,
Expression* b,
const T& aMap,
const T& bMap,
const V defaultValue) {
auto aIter = aMap.find(a);
auto aItem = aIter != aMap.end() ? aIter->second : defaultValue;
auto bIter = bMap.find(b);
auto bItem = bIter != bMap.end() ? bIter->second : defaultValue;
return aItem == bItem;
}
bool equal(Function* a, Function* b) {
if (a->imported() && b->imported()) {
// No code metadata, and we don't yet store function-level metadata.
return true;
}
if (a->imported() || b->imported()) {
// See comment on declaration, we consider such a difference as making them
// unequal.
return false;
}
// TODO: We do not consider debug locations here. This is often what is
// desired in optimized builds (e.g. if we are trying to fold two
// pieces of code together, that benefit outweighs slightly inaccurate
// debug info). If we find that non-optimizer locations call this in
// ways that lead to degraded debug info, we could add an option to
// control it.
if (a->codeAnnotations.empty() && b->codeAnnotations.empty()) {
// Nothing to compare; no differences.
return true;
}
Serializer aList(a->body);
Serializer bList(b->body);
if (aList.list.size() != bList.list.size()) {
return false;
}
assert(aList.list.size() == bList.list.size());
for (Index i = 0; i < aList.list.size(); i++) {
if (!compare(aList.list[i],
bList.list[i],
a->codeAnnotations,
b->codeAnnotations,
Function::CodeAnnotation())) {
return false;
}
}
return true;
}
#pragma GCC diagnostic pop
} // namespace wasm::metadata