summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/asm_v_wasm.h24
-rw-r--r--src/asmjs/asm_v_wasm.cpp23
-rw-r--r--src/wasm-s-parser.h14
-rw-r--r--src/wasm.h1
-rw-r--r--src/wasm/wasm-s-parser.cpp379
-rw-r--r--src/wasm/wasm.cpp11
6 files changed, 242 insertions, 210 deletions
diff --git a/src/asm_v_wasm.h b/src/asm_v_wasm.h
index 66601cf10..95b4cbbca 100644
--- a/src/asm_v_wasm.h
+++ b/src/asm_v_wasm.h
@@ -29,8 +29,17 @@ AsmType wasmToAsmType(Type type);
char getSig(Type type);
-std::string getSig(const FunctionType* type);
+template<typename ListType>
+std::string getSig(const ListType& params, Type result) {
+ std::string ret;
+ ret += getSig(result);
+ for (auto param : params) {
+ ret += getSig(param);
+ }
+ return ret;
+}
+std::string getSig(const FunctionType* type);
std::string getSig(Function* func);
template<typename T,
@@ -67,9 +76,18 @@ std::string getSigFromStructs(Type result, const ListType& operands) {
Type sigToType(char sig);
-FunctionType sigToFunctionType(std::string sig);
+FunctionType sigToFunctionType(const std::string& sig);
+
+FunctionType*
+ensureFunctionType(const std::string& sig, Module* wasm, Name name = Name());
-FunctionType* ensureFunctionType(std::string sig, Module* wasm);
+template<typename ListType>
+FunctionType* ensureFunctionType(const ListType& params,
+ Type result,
+ Module* wasm,
+ Name name = Name()) {
+ return ensureFunctionType(getSig(params, result), wasm, name);
+}
// converts an f32 to an f64 if necessary
Expression* ensureDouble(Expression* expr, MixedArena& allocator);
diff --git a/src/asmjs/asm_v_wasm.cpp b/src/asmjs/asm_v_wasm.cpp
index aa247153d..af22112a3 100644
--- a/src/asmjs/asm_v_wasm.cpp
+++ b/src/asmjs/asm_v_wasm.cpp
@@ -86,21 +86,11 @@ char getSig(Type type) {
}
std::string getSig(const FunctionType* type) {
- std::string ret;
- ret += getSig(type->result);
- for (auto param : type->params) {
- ret += getSig(param);
- }
- return ret;
+ return getSig(type->params, type->result);
}
std::string getSig(Function* func) {
- std::string ret;
- ret += getSig(func->result);
- for (auto type : func->params) {
- ret += getSig(type);
- }
- return ret;
+ return getSig(func->params, func->result);
}
Type sigToType(char sig) {
@@ -124,7 +114,7 @@ Type sigToType(char sig) {
}
}
-FunctionType sigToFunctionType(std::string sig) {
+FunctionType sigToFunctionType(const std::string& sig) {
FunctionType ret;
ret.result = sigToType(sig[0]);
for (size_t i = 1; i < sig.size(); i++) {
@@ -133,8 +123,11 @@ FunctionType sigToFunctionType(std::string sig) {
return ret;
}
-FunctionType* ensureFunctionType(std::string sig, Module* wasm) {
- cashew::IString name(("FUNCSIG$" + sig).c_str(), false);
+FunctionType*
+ensureFunctionType(const std::string& sig, Module* wasm, Name name) {
+ if (!name.is()) {
+ name = "FUNCSIG$" + sig;
+ }
if (wasm->getFunctionTypeOrNull(name)) {
return wasm->getFunctionType(name);
}
diff --git a/src/wasm-s-parser.h b/src/wasm-s-parser.h
index 2e84b2245..d501a349a 100644
--- a/src/wasm-s-parser.h
+++ b/src/wasm-s-parser.h
@@ -112,7 +112,6 @@ class SExpressionWasmBuilder {
Module& wasm;
MixedArena& allocator;
std::vector<Name> functionNames;
- std::vector<Name> functionTypeNames;
std::vector<Name> globalNames;
int functionCounter;
int globalCounter = 0;
@@ -222,9 +221,20 @@ private:
Type parseOptionalResultType(Element& s, Index& i);
Index parseMemoryLimits(Element& s, Index i);
std::vector<Type> parseParamOrLocal(Element& s);
- std::vector<NameType> parseNamedParamOrLocal(Element& s, size_t& localIndex);
+ std::vector<NameType> parseParamOrLocal(Element& s, size_t& localIndex);
Type parseResult(Element& s);
FunctionType* parseTypeRef(Element& s);
+ size_t parseTypeUse(Element& s,
+ size_t startPos,
+ FunctionType*& functionType,
+ std::vector<NameType>& namedParams,
+ Type& result);
+ size_t parseTypeUse(Element& s,
+ size_t startPos,
+ FunctionType*& functionType,
+ std::vector<Type>& params,
+ Type& result);
+ size_t parseTypeUse(Element& s, size_t startPos, FunctionType*& functionType);
void stringToBinary(const char* input, size_t size, std::vector<char>& data);
void parseMemory(Element& s, bool preParseImport = false);
diff --git a/src/wasm.h b/src/wasm.h
index 10f00f0b7..d4ffe86f2 100644
--- a/src/wasm.h
+++ b/src/wasm.h
@@ -632,6 +632,7 @@ public:
FunctionType() = default;
bool structuralComparison(FunctionType& b);
+ bool structuralComparison(const std::vector<Type>& params, Type result);
bool operator==(FunctionType& b);
bool operator!=(FunctionType& b);
diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp
index 9b338931e..c3a8fb940 100644
--- a/src/wasm/wasm-s-parser.cpp
+++ b/src/wasm/wasm-s-parser.cpp
@@ -453,10 +453,10 @@ Name SExpressionWasmBuilder::getFunctionTypeName(Element& s) {
} else {
// index
size_t offset = atoi(s.str().c_str());
- if (offset >= functionTypeNames.size()) {
+ if (offset >= wasm.functionTypes.size()) {
throw ParseException("unknown function type in getFunctionTypeName");
}
- return functionTypeNames[offset];
+ return wasm.functionTypes[offset]->name;
}
}
@@ -477,10 +477,10 @@ Name SExpressionWasmBuilder::getGlobalName(Element& s) {
// parameter or local names when specified.
std::vector<Type> SExpressionWasmBuilder::parseParamOrLocal(Element& s) {
size_t fakeIndex = 0;
- std::vector<NameType> namedParams = parseNamedParamOrLocal(s, fakeIndex);
+ std::vector<NameType> namedParams = parseParamOrLocal(s, fakeIndex);
std::vector<Type> params;
- for (auto& nameType : namedParams) {
- params.push_back(nameType.type);
+ for (auto& p : namedParams) {
+ params.push_back(p.type);
}
return params;
}
@@ -492,7 +492,7 @@ std::vector<Type> SExpressionWasmBuilder::parseParamOrLocal(Element& s) {
// (local type+) (e.g. (local i32 f64))
// If the name is unspecified, it will create one using localIndex.
std::vector<NameType>
-SExpressionWasmBuilder::parseNamedParamOrLocal(Element& s, size_t& localIndex) {
+SExpressionWasmBuilder::parseParamOrLocal(Element& s, size_t& localIndex) {
assert(elementStartsWith(s, PARAM) || elementStartsWith(s, LOCAL));
std::vector<NameType> namedParams;
if (s.size() == 1) { // (param) or (local)
@@ -538,11 +538,109 @@ FunctionType* SExpressionWasmBuilder::parseTypeRef(Element& s) {
throw ParseException("invalid type reference", s.line, s.col);
}
IString name = getFunctionTypeName(*s[1]);
- FunctionType* ft = wasm.getFunctionTypeOrNull(name);
- if (!ft) {
+ FunctionType* functionType = wasm.getFunctionTypeOrNull(name);
+ if (!functionType) {
throw ParseException("bad function type for import", s[1]->line, s[1]->col);
}
- return ft;
+ return functionType;
+}
+
+// Prases typeuse, a reference to a type definition. It is in the form of either
+// (type index) or (type name), possibly augmented by inlined (param) and
+// (result) nodes. (type) node can be omitted as well, in which case we get an
+// existing type if there's one with the same structure or create one.
+// Outputs are returned by parameter references.
+// typeuse ::= (type index|name)+ |
+// (type index|name)+ (param ..)* (result ..)* |
+// (param ..)* (result ..)*
+// TODO Remove FunctionType* parameter and the related logic to create
+// FunctionType once we remove FunctionType class.
+size_t SExpressionWasmBuilder::parseTypeUse(Element& s,
+ size_t startPos,
+ FunctionType*& functionType,
+ std::vector<NameType>& namedParams,
+ Type& result) {
+ size_t i = startPos;
+ bool typeExists = false, paramOrResultExists = false;
+ if (i < s.size() && elementStartsWith(*s[i], TYPE)) {
+ typeExists = true;
+ functionType = parseTypeRef(*s[i++]);
+ }
+ size_t paramPos = i;
+
+ size_t localIndex = 0;
+ while (i < s.size() && elementStartsWith(*s[i], PARAM)) {
+ paramOrResultExists = true;
+ auto newParams = parseParamOrLocal(*s[i++], localIndex);
+ namedParams.insert(namedParams.end(), newParams.begin(), newParams.end());
+ }
+ result = none;
+ if (i < s.size() && elementStartsWith(*s[i], RESULT)) {
+ paramOrResultExists = true;
+ result = parseResult(*s[i++]);
+ }
+
+ // verify if (type) and (params)/(result) match, if both are specified
+ if (typeExists && paramOrResultExists) {
+ size_t line = s[paramPos]->line, col = s[paramPos]->col;
+ const char* msg = "type and param/result don't match";
+ if (functionType->result != result) {
+ throw ParseException(msg, line, col);
+ }
+ if (functionType->params.size() != namedParams.size()) {
+ throw ParseException(msg, line, col);
+ }
+ for (size_t i = 0, n = namedParams.size(); i < n; i++) {
+ if (functionType->params[i] != namedParams[i].type) {
+ throw ParseException(msg, line, col);
+ }
+ }
+ }
+
+ // If (type) does not exist, check if there's a matching type, and if there
+ // isn't, create one.
+ if (!typeExists) {
+ bool need = true;
+ std::vector<Type> params;
+ for (auto& p : namedParams) {
+ params.push_back(p.type);
+ }
+ for (auto& existing : wasm.functionTypes) {
+ if (existing->structuralComparison(params, result)) {
+ functionType = existing.get();
+ need = false;
+ break;
+ }
+ }
+ if (need) {
+ functionType = ensureFunctionType(params, result, &wasm);
+ }
+ }
+
+ return i;
+}
+
+// Parses a typeuse. Ignores all parameter names.
+size_t SExpressionWasmBuilder::parseTypeUse(Element& s,
+ size_t startPos,
+ FunctionType*& functionType,
+ std::vector<Type>& params,
+ Type& result) {
+ std::vector<NameType> namedParams;
+ size_t nextPos = parseTypeUse(s, startPos, functionType, namedParams, result);
+ for (auto& p : namedParams) {
+ params.push_back(p.type);
+ }
+ return nextPos;
+}
+
+// Parses a typeuse. Use this when only FunctionType* is needed.
+size_t SExpressionWasmBuilder::parseTypeUse(Element& s,
+ size_t startPos,
+ FunctionType*& functionType) {
+ std::vector<Type> params;
+ Type result;
+ return parseTypeUse(s, startPos, functionType, params, result);
}
void SExpressionWasmBuilder::preParseFunctionType(Element& s) {
@@ -565,41 +663,9 @@ void SExpressionWasmBuilder::preParseFunctionType(Element& s) {
FunctionType* type = nullptr;
functionTypes[name] = none;
std::vector<Type> params;
- for (; i < s.size(); i++) {
- Element& curr = *s[i];
- IString id = curr[0]->str();
- if (id == RESULT) {
- functionTypes[name] = parseResult(curr);
- } else if (id == TYPE) {
- functionTypes[name] = parseTypeRef(curr)->result;
- } else if (id == PARAM && curr.size() > 1) {
- auto newParams = parseParamOrLocal(curr);
- params.insert(params.end(), newParams.begin(), newParams.end());
- }
- }
- if (!type) {
- // if no function type provided, generate one, but reuse a previous one with
- // the right structure if there is one.
- // see https://github.com/WebAssembly/spec/pull/301
- bool need = true;
- std::unique_ptr<FunctionType> functionType = make_unique<FunctionType>();
- functionType->result = functionTypes[name];
- functionType->params = std::move(params);
- for (auto& existing : wasm.functionTypes) {
- if (existing->structuralComparison(*functionType)) {
- need = false;
- break;
- }
- }
- if (need) {
- functionType->name = Name::fromInt(wasm.functionTypes.size());
- functionTypeNames.push_back(functionType->name);
- if (wasm.getFunctionTypeOrNull(functionType->name)) {
- throw ParseException("duplicate function type", s.line, s.col);
- }
- wasm.addFunctionType(std::move(functionType));
- }
- }
+ parseTypeUse(s, i, type);
+ assert(type && "type should've been set by parseTypeUse");
+ functionTypes[name] = type->result;
}
size_t SExpressionWasmBuilder::parseFunctionNames(Element& s,
@@ -634,9 +700,10 @@ size_t SExpressionWasmBuilder::parseFunctionNames(Element& s,
}
void SExpressionWasmBuilder::parseFunction(Element& s, bool preParseImport) {
- size_t i = 1;
+ brokeToAutoBlock = false;
+
Name name, exportName;
- i = parseFunctionNames(s, name, exportName);
+ size_t i = parseFunctionNames(s, name, exportName);
if (!preParseImport) {
if (!name.is()) {
// unnamed, use an index
@@ -660,86 +727,26 @@ void SExpressionWasmBuilder::parseFunction(Element& s, bool preParseImport) {
}
wasm.addExport(ex.release());
}
- Expression* body = nullptr;
- size_t localIndex = 0; // params and vars
- brokeToAutoBlock = false;
- // we may have both params and a type. store the type info here
- std::vector<NameType> typeParams;
- std::vector<NameType> params;
- std::vector<NameType> vars;
- Type result = none;
- Name type;
- // we may need to add a block for the very top level
- Block* autoBlock = nullptr;
+
+ // parse import
Name importModule, importBase;
- auto makeFunction = [&]() {
- currFunction = std::unique_ptr<Function>(Builder(wasm).makeFunction(
- name, std::move(params), result, std::move(vars)));
- };
- auto ensureAutoBlock = [&]() {
- if (!autoBlock) {
- autoBlock = allocator.alloc<Block>();
- autoBlock->list.push_back(body);
- body = autoBlock;
- }
- };
- for (; i < s.size(); i++) {
+ if (i < s.size() && elementStartsWith(*s[i], IMPORT)) {
Element& curr = *s[i];
- IString id = curr[0]->str();
- if (id == PARAM) {
- auto newParams = parseNamedParamOrLocal(curr, localIndex);
- params.insert(params.end(), newParams.begin(), newParams.end());
- } else if (id == LOCAL) {
- auto newVars = parseNamedParamOrLocal(curr, localIndex);
- vars.insert(vars.end(), newVars.begin(), newVars.end());
- } else if (id == RESULT) {
- result = parseResult(curr);
- } else if (id == TYPE) {
- FunctionType* ft = parseTypeRef(curr);
- type = ft->name;
- result = ft->result;
- for (size_t j = 0; j < ft->params.size(); j++) {
- IString name = Name::fromInt(j);
- Type currType = ft->params[j];
- typeParams.emplace_back(name, currType);
- }
- } else if (id == IMPORT) {
- importModule = curr[1]->str();
- importBase = curr[2]->str();
- } else {
- // body
- if (typeParams.size() > 0 && params.size() == 0) {
- params = typeParams;
- }
- if (!currFunction) {
- makeFunction();
- }
- Expression* ex = parseExpression(curr);
- if (!body) {
- body = ex;
- } else {
- ensureAutoBlock();
- autoBlock->list.push_back(ex);
- }
- }
- }
- // see https://github.com/WebAssembly/spec/pull/301
- if (type.isNull()) {
- // if no function type name provided, then we generated one
- auto functionType = make_unique<FunctionType>(
- sigToFunctionType(getSigFromStructs(result, params)));
- for (auto& existing : wasm.functionTypes) {
- if (existing->structuralComparison(*functionType)) {
- type = existing->name;
- break;
- }
- }
- if (!type.is()) {
- throw ParseException("no function type [internal error?]", s.line, s.col);
- }
+ importModule = curr[1]->str();
+ importBase = curr[2]->str();
+ i++;
}
+
+ // parse typeuse: type/param/result
+ FunctionType* functionType = nullptr;
+ std::vector<NameType> params;
+ Type result = none;
+ i = parseTypeUse(s, i, functionType, params, result);
+ assert(functionType && "functionType should've been set by parseTypeUse");
+
+ // when (import) is inside a (func) element, this is not a function definition
+ // but an import.
if (importModule.is()) {
- // this is an import, actually
if (!importBase.size()) {
throw ParseException("module but no base for import");
}
@@ -750,8 +757,8 @@ void SExpressionWasmBuilder::parseFunction(Element& s, bool preParseImport) {
im->name = name;
im->module = importModule;
im->base = importBase;
- im->type = type;
- FunctionTypeUtils::fillFunction(im.get(), wasm.getFunctionType(type));
+ im->type = functionType->name;
+ FunctionTypeUtils::fillFunction(im.get(), functionType);
functionTypes[name] = im->result;
if (wasm.getFunctionOrNull(im->name)) {
throw ParseException("duplicate import", s.line, s.col);
@@ -763,9 +770,52 @@ void SExpressionWasmBuilder::parseFunction(Element& s, bool preParseImport) {
nameMapper.clear();
return;
}
+ // at this point this not an import but a real function definition.
if (preParseImport) {
throw ParseException("preParseImport in func");
}
+
+ // in case only (type) is specified, populate params and result with temporary
+ // names
+ if (params.empty()) {
+ for (size_t j = 0; j < functionType->params.size(); j++) {
+ params.emplace_back(Name::fromInt(j), functionType->params[j]);
+ }
+ }
+ result = functionType->result;
+ size_t localIndex = params.size(); // local index for params and locals
+
+ // parse locals
+ std::vector<NameType> vars;
+ while (i < s.size() && elementStartsWith(*s[i], LOCAL)) {
+ auto newVars = parseParamOrLocal(*s[i++], localIndex);
+ vars.insert(vars.end(), newVars.begin(), newVars.end());
+ }
+
+ // make a new function
+ currFunction = std::unique_ptr<Function>(Builder(wasm).makeFunction(
+ name, std::move(params), result, std::move(vars)));
+ currFunction->type = functionType->name;
+
+ // parse body
+ Block* autoBlock = nullptr; // may need to add a block for the very top level
+ auto ensureAutoBlock = [&]() {
+ if (!autoBlock) {
+ autoBlock = allocator.alloc<Block>();
+ autoBlock->list.push_back(currFunction->body);
+ currFunction->body = autoBlock;
+ }
+ };
+ while (i < s.size()) {
+ Expression* ex = parseExpression(*s[i++]);
+ if (!currFunction->body) {
+ currFunction->body = ex;
+ } else {
+ ensureAutoBlock();
+ autoBlock->list.push_back(ex);
+ }
+ }
+
if (brokeToAutoBlock) {
ensureAutoBlock();
autoBlock->name = FAKE_RETURN;
@@ -773,15 +823,12 @@ void SExpressionWasmBuilder::parseFunction(Element& s, bool preParseImport) {
if (autoBlock) {
autoBlock->finalize(result);
}
- if (!currFunction) {
- makeFunction();
- body = allocator.alloc<Nop>();
+ if (!currFunction->body) {
+ currFunction->body = allocator.alloc<Nop>();
}
if (currFunction->result != result) {
throw ParseException("bad func declaration", s.line, s.col);
}
- currFunction->body = body;
- currFunction->type = type;
if (s.startLoc) {
currFunction->prologLocation.insert(getDebugLocation(*s.startLoc));
}
@@ -1551,35 +1598,11 @@ Expression* SExpressionWasmBuilder::makeCallIndirect(Element& s) {
}
auto ret = allocator.alloc<CallIndirect>();
Index i = 1;
- Element& typeElement = *s[i];
- if (elementStartsWith(typeElement, TYPE)) {
- // type name given
- IString type = typeElement[1]->str();
- auto* fullType = wasm.getFunctionTypeOrNull(type);
- if (!fullType) {
- throw ParseException("invalid call_indirect type", s.line, s.col);
- }
- ret->fullType = fullType->name;
- i++;
- } else {
- // inline type
- FunctionType type;
- while (1) {
- Element& curr = *s[i];
- if (elementStartsWith(curr, PARAM)) {
- auto newParams = parseParamOrLocal(curr);
- type.params.insert(
- type.params.end(), newParams.begin(), newParams.end());
- } else if (elementStartsWith(curr, RESULT)) {
- type.result = parseResult(curr);
- } else {
- break;
- }
- i++;
- }
- ret->fullType = ensureFunctionType(getSig(&type), &wasm)->name;
- }
- ret->type = wasm.getFunctionType(ret->fullType)->result;
+ FunctionType* functionType = nullptr;
+ i = parseTypeUse(s, i, functionType);
+ assert(functionType && "functionType should've been set by parseTypeUse");
+ ret->fullType = functionType->name;
+ ret->type = functionType->result;
parseCallOperands(s, i, s.size() - 1, ret);
ret->target = parseExpression(s[s.size() - 1]);
ret->finalize();
@@ -1924,36 +1947,13 @@ void SExpressionWasmBuilder::parseImport(Element& s) {
Element& inner = newStyle ? *s[3] : s;
Index j = newStyle ? newStyleInner : i;
if (kind == ExternalKind::Function) {
- std::unique_ptr<FunctionType> type = make_unique<FunctionType>();
- if (inner.size() > j) {
- Element& elems = *inner[j];
- IString id = elems[0]->str();
- if (id == PARAM) {
- auto newParams = parseParamOrLocal(elems);
- type->params.insert(
- type->params.end(), newParams.begin(), newParams.end());
- } else if (id == RESULT) {
- type->result = parseResult(elems);
- } else if (id == TYPE) {
- type.reset(parseTypeRef(elems));
- } else {
- throw ParseException("bad import element");
- }
- if (inner.size() > j + 1) {
- Element& result = *inner[j + 1];
- if (result[0]->str() != RESULT) {
- throw ParseException("expected result");
- }
- type->result = parseResult(result);
- }
- }
+ FunctionType* functionType = nullptr;
auto func = make_unique<Function>();
+ parseTypeUse(inner, j, functionType, func->params, func->result);
func->name = name;
func->module = module;
func->base = base;
- auto* functionType = ensureFunctionType(getSig(type.get()), &wasm);
func->type = functionType->name;
- FunctionTypeUtils::fillFunction(func.get(), functionType);
functionTypes[name] = func->result;
wasm.addFunction(func.release());
} else if (kind == ExternalKind::Global) {
@@ -2211,12 +2211,17 @@ void SExpressionWasmBuilder::parseType(Element& s) {
type->result = parseResult(curr);
}
}
+ while (type->name.is() && wasm.getFunctionTypeOrNull(type->name)) {
+ throw ParseException("duplicate function type", s.line, s.col);
+ }
+ // We allow duplicate types in the type section, i.e., we can have
+ // (func (param i32) (result i32)) many times. For unnamed types, find a name
+ // that does not clash with existing ones.
if (!type->name.is()) {
- type->name = Name::fromInt(wasm.functionTypes.size());
+ type->name = "FUNCSIG$" + getSig(type.get());
}
- functionTypeNames.push_back(type->name);
- if (wasm.getFunctionTypeOrNull(type->name)) {
- throw ParseException("duplicate function type", s.line, s.col);
+ while (wasm.getFunctionTypeOrNull(type->name)) {
+ type->name = Name(std::string(type->name.c_str()) + "_");
}
wasm.addFunctionType(std::move(type));
}
diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp
index c72103ebe..c32ead836 100644
--- a/src/wasm/wasm.cpp
+++ b/src/wasm/wasm.cpp
@@ -406,14 +406,19 @@ void CallIndirect::finalize() {
}
bool FunctionType::structuralComparison(FunctionType& b) {
- if (result != b.result) {
+ return structuralComparison(b.params, b.result);
+}
+
+bool FunctionType::structuralComparison(const std::vector<Type>& otherParams,
+ Type otherResult) {
+ if (result != otherResult) {
return false;
}
- if (params.size() != b.params.size()) {
+ if (params.size() != otherParams.size()) {
return false;
}
for (size_t i = 0; i < params.size(); i++) {
- if (params[i] != b.params[i]) {
+ if (params[i] != otherParams[i]) {
return false;
}
}