blob: 2e53b534b8474f7cc596217802e04740965b59d5 [file] [log] [blame]
/*
* Copyright (C) 2016-2017 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
"use strict";
/*
* Base utility types for the AST.
*
* Valid methods for Node:
*
* node.children -> Returns an array of immediate children. It has been modified
* from the original Ruby version to dump directly nearly the original source.
*
* node.descendents -> Returns an array of all strict descendants (children
* and children of children, transitively).
*
*/
class Node
{
constructor(codeOrigin)
{
this._codeOrigin = codeOrigin;
}
get codeOriginString()
{
return this._codeOrigin.toString();
}
get codeOrigin()
{
return this._codeOrigin;
}
}
class NoChildren extends Node
{
constructor(codeOrigin)
{
super(codeOrigin);
}
children()
{
return [];
}
}
function structOffsetKey(struct, field)
{
return struct + "::" + field;
}
// AST nodes.
class StructOffset extends NoChildren
{
constructor(codeOrigin, struct, field)
{
super(codeOrigin);
this._struct = struct;
this._field = field;
}
static forField(codeOrigin, struct, field)
{
let key = structOffsetKey(struct, field);
if (!this.mapping[key])
this.mapping[key] = new StructOffset(codeOrigin, struct, field);
return this.mapping[key];
}
static resetMappings()
{
this.mapping = {};
}
dump()
{
return this.struct + "::" + this.field;
}
compare(other)
{
if (this.struct != other.struct)
return this.struct.localeCompare(other.struct);
return this.field.localeCompare(other.field);
}
get struct()
{
return this._struct;
}
get field()
{
return this._field;
}
get isAddress()
{
return false;
}
get isLabel()
{
return false;
}
get isImmediate()
{
return true;
}
get isRegister()
{
return false;
}
}
StructOffset.mapping = {};
class Sizeof extends NoChildren
{
constructor(codeOrigin, struct)
{
super(codeOrigin);
this._struct = struct;
}
static forName(codeOrigin, struct)
{
if (!this.mapping[struct])
this.mapping[struct] = new Sizeof(codeOrigin, struct);
return this.mapping[struct];
}
static resetMappings()
{
this.mapping = {};
}
dump()
{
return "sizeof " + this.struct;
}
compare(other)
{
return this.struct.localeCompare(other.struct);
}
get struct()
{
return this._struct;
}
get isAddress()
{
return false;
}
get isLabel()
{
return false;
}
get isImmediate()
{
return true;
}
get isRegister()
{
return false;
}
}
Sizeof.mapping = {};
class Immediate extends NoChildren
{
constructor(codeOrigin, value)
{
super(codeOrigin);
if (value instanceof Number)
value = value.valueOf();
this._value = value;
if (typeof(value) != "number")
throw "Bad immediate value " + value.inspect() + " at " + this.codeOriginString();
}
dump()
{
return this.value.toString();
}
equals(other)
{
return other instanceof Immediate && other.value == this.value;
}
get value()
{
return this._value;
}
get isAddress()
{
return false;
}
get isLabel()
{
return false;
}
get isImmediate()
{
return true;
}
get isImmediateOperand()
{
return true;
}
get isRegister()
{
return false;
}
}
class AddImmediates extends Node
{
constructor(codeOrigin, left, right)
{
super(codeOrigin);
this._left = left;
this._right = right;
}
children()
{
return [this.left, this.right];
}
dump()
{
return "(" + this.left.dump() + " + " + this.right.dump() + ")";
}
value()
{
return (this.left.value() + this.right.value()).toString();
}
get left()
{
return this._left;
}
get right()
{
return this._right;
}
get isAddress()
{
return false;
}
get isLabel()
{
return false;
}
get isImmediate()
{
return true;
}
get isImmediateOperand()
{
return true;
}
get isRegister()
{
return false;
}
}
class SubImmediates extends Node
{
constructor(codeOrigin, left, right)
{
super(codeOrigin);
this._left = left;
this._right = right;
}
children()
{
return [this.left, this.right];
}
dump()
{
return "(" + this.left.dump() + " - " + this.right.dump() + ")";
}
value()
{
return (this.left.value() - this.right.value()).toString();
}
get left()
{
return this._left;
}
get right()
{
return this._right;
}
get isAddress()
{
return false;
}
get isLabel()
{
return false;
}
get isImmediate()
{
return true;
}
get isImmediateOperand()
{
return true;
}
get isRegister()
{
return false;
}
}
class MulImmediates extends Node
{
constructor(codeOrigin, left, right)
{
super(codeOrigin);
this._left = left;
this._right = right;
}
children()
{
return [this.left, this.right];
}
dump()
{
return "(" + this.left.dump() + " * " + this.right.dump() + ")";
}
get left()
{
return this._left;
}
get right()
{
return this._right;
}
get isAddress()
{
return false;
}
get isLabel()
{
return false;
}
get isImmediate()
{
return true;
}
get isImmediateOperand()
{
return false;
}
get isRegister()
{
return false;
}
}
class NegImmediate extends Node
{
constructor(codeOrigin, child)
{
super(codeOrigin);
this._child = child;
}
children()
{
return [this.child];
}
dump()
{
return "(-" + this.child.dump() + ")";
}
get child()
{
return this._child;
}
get isAddress()
{
return false;
}
get isLabel()
{
return false;
}
get isImmediate()
{
return true;
}
get isImmediateOperand()
{
return false;
}
get isRegister()
{
return false;
}
}
class OrImmediates extends Node
{
constructor(codeOrigin, left, right)
{
super(codeOrigin);
this._left = left;
this._right = right;
}
children()
{
return [this.left, this.right];
}
dump()
{
return "(" + this.left.dump() + " | " + this.right.dump() + ")";
}
get left()
{
return this._left;
}
get right()
{
return this._right;
}
get isAddress()
{
return false;
}
get isLabel()
{
return false;
}
get isImmediate()
{
return true;
}
get isImmediateOperand()
{
return false;
}
get isRegister()
{
return false;
}
}
class AndImmediates extends Node
{
constructor(codeOrigin, left, right)
{
super(codeOrigin);
this._left = left;
this._right = right;
}
children()
{
return [this.left, this.right];
}
dump()
{
return "(" + this.left.dump() + " & " + this.right.dump() + ")";
}
get left()
{
return this._left;
}
get right()
{
return this._right;
}
get isAddress()
{
return false;
}
get isLabel()
{
return false;
}
get isImmediate()
{
return true;
}
get isImmediateOperand()
{
return false;
}
get isRegister()
{
return false;
}
}
class XorImmediates extends Node
{
constructor(codeOrigin, left, right)
{
super(codeOrigin);
this._left = left;
this._right = right;
}
children()
{
return [this.left, this.right];
}
dump()
{
return "(" + this.left.dump() + " ^ " + this.right.dump() + ")";
}
get left()
{
return this._left;
}
get right()
{
return this._right;
}
get isAddress()
{
return false;
}
get isLabel()
{
return false;
}
get isImmediate()
{
return true;
}
get isImmediateOperand()
{
return false;
}
get isRegister()
{
return false;
}
}
class BitnotImmediate extends Node
{
constructor(codeOrigin, child)
{
super(codeOrigin);
this._child = child;
}
children()
{
return [this.child];
}
dump()
{
return "(~" + this.child.dump() + ")";
}
get child()
{
return this._child;
}
get isAddress()
{
return false;
}
get isLabel()
{
return false;
}
get isImmediate()
{
return true;
}
get isImmediateOperand()
{
return false;
}
get isRegister()
{
return false;
}
}
class StringLiteral extends NoChildren
{
constructor(codeOrigin, value)
{
super(codeOrigin);
if (!value instanceof String || value[0] != "\"" || value.slice(-1) != "\"")
throw "Bad string literal " + value.inspect() + " at " + this.codeOriginString();
this._value = value.slice(1, -1);
}
dump()
{
return "\"" + this.value + "\"";
}
equals(other)
{
return other instanceof StringLiteral && other.value == this.value;
}
get value()
{
return this._value;
}
get isAddress()
{
return false;
}
get isLabel()
{
return false;
}
get isImmediate()
{
return false;
}
get isImmediateOperand()
{
return false;
}
get isRegister()
{
return false;
}
}
class RegisterID extends NoChildren
{
constructor(codeOrigin, name)
{
super(codeOrigin);
this._name = name;
}
static forName(codeOrigin, name)
{
if (!this.mapping[name])
this.mapping[name] = new RegisterID(codeOrigin, name);
return this.mapping[name];
}
dump()
{
return this.name;
}
get name()
{
return this._name;
}
get isAddress()
{
return false;
}
get isLabel()
{
return false;
}
get isImmediate()
{
return false;
}
get isRegister()
{
return true;
}
}
RegisterID.mapping = {};
class FPRegisterID extends NoChildren
{
constructor(codeOrigin, name)
{
super(codeOrigin);
this._name = name;
}
static forName(codeOrigin, name)
{
if (!this.mapping[name])
this.mapping[name] = new FPRegisterID(codeOrigin, name);
return this.mapping[name];
}
dump()
{
return this.name;
}
get name()
{
return this._name;
}
get isAddress()
{
return false;
}
get isLabel()
{
return false;
}
get isImmediate()
{
return false;
}
get isImmediateOperand()
{
return false;
}
get isRegister()
{
return true;
}
}
FPRegisterID.mapping = {};
class SpecialRegister extends NoChildren
{
constructor(name)
{
this._name = name;
}
get isAddress()
{
return false;
}
get isLabel()
{
return false;
}
get isImmediate()
{
return false;
}
get isImmediateOperand()
{
return false;
}
get isRegister()
{
return true;
}
}
class Variable extends NoChildren
{
constructor(codeOrigin, name)
{
super(codeOrigin);
this._name = name;
}
static forName(codeOrigin, name)
{
if (!this.mapping[name])
this.mapping[name] = new Variable(codeOrigin, name);
return this.mapping[name];
}
dump()
{
return this.name;
}
get name()
{
return this._name;
}
inspect()
{
return "<variable " + this.name + " at " + this.codeOriginString();
}
}
Variable.mapping = {};
class Address extends Node
{
constructor(codeOrigin, base, offset)
{
super(codeOrigin);
this._base = base;
this._offset = offset;
if (!base instanceof Variable && !base.register)
throw "Bad base for address " + base.inspect() + " at " + this.codeOriginString();
if (!offset instanceof Variable && !offset.immediate())
throw "Bad offset for address " + offset.inspect() + " at " + this.codeOriginString();
}
withOffset(extraOffset)
{
return new Address(this.codeOrigin, this.base, new Immediate(this.codeOrigin, this.offset.value + extraOffset));
}
children()
{
return [this.base, this.offset];
}
dump()
{
return this.offset.dump() + "[" + this.base.dump() + "]";
}
get base()
{
return this._base;
}
get offset()
{
return this._offset;
}
get isAddress()
{
return true;
}
get isLabel()
{
return false;
}
get isImmediate()
{
return false;
}
get isImmediateOperand()
{
return true;
}
get isRegister()
{
return false;
}
}
class BaseIndex extends Node
{
constructor(codeOrigin, base, index, scale, offset)
{
super(codeOrigin);
this._base = base;
this._index = index;
this._scale = scale;
if (![1, 2, 4, 8].includes(this._scale))
throw "Bad scale " + this._scale + " at " + this.codeOriginString();
this._offset = offset;
}
scaleShift()
{
switch(this.scale)
{
case 1:
return 0;
case 2:
return 1;
case 4:
return 2;
case 8:
return 3;
default:
throw "Bad scale " + this.scale + " at " + this.codeOriginString();
}
}
withOffset(extraOffset)
{
return new BaseIndex(codeOrigin, this.base, this.index, this.scale, new Immediate(codeOrigin, this.offset.value + extraOffset));
}
children()
{
return [this.base, this.index, this.offset];
}
dump()
{
return this.offset.dump() + "[" + this.base.dump() + ", " + this.index.dump() + ", " + this.scale + "]";
}
get base()
{
return this._base;
}
get index()
{
return this._index;
}
get scale()
{
return this._scale;
}
get offset()
{
return this._offset;
}
get isAddress()
{
return true;
}
get isLabel()
{
return false;
}
get isImmediate()
{
return false;
}
get isImmediateOperand()
{
return false;
}
get isRegister()
{
return false;
}
}
class AbsoluteAddress extends NoChildren
{
constructor(codeOrigin, address)
{
super(codeOrigin);
this._address = address;
}
withOffset(extraOffset)
{
return new AbsoluteAddress(codeOrigin, new Immediate(codeOrigin, this.address.value + extraOffset));
}
dump()
{
return this.address.dump() + "[]";
}
get address()
{
return this._address;
}
get isAddress()
{
return true;
}
get isLabel()
{
return false;
}
get isImmediate()
{
return false;
}
get isImmediateOperand()
{
return true;
}
get isRegister()
{
return false;
}
}
class Instruction extends Node
{
constructor(codeOrigin, opcode, operands, annotation=nil)
{
super(codeOrigin);
this._opcode = opcode;
this._operands = operands;
this._annotation = annotation;
}
children()
{
return [];
}
dump()
{
return " " + this.opcode + " " + (this.operands.map(function(v) { return v.dump(); }).join(", "));
}
get opcode()
{
return this._opcode;
}
get operands()
{
return this._operands;
}
get annotation()
{
return this._annotation;
}
}
class Error extends NoChildren
{
constructor(codeOrigin)
{
super(codeOrigin);
}
dump()
{
return " error";
}
}
class ConstExpr extends NoChildren
{
constructor(codeOrigin, value)
{
super(codeOrigin);
this._value = value;
}
static forName(codeOrigin, text)
{
if (!this.mapping[text])
this.mapping[text] = new ConstExpr(codeOrigin, text);
return this.mapping[text];
}
static resetMappings()
{
this.mapping = {};
}
dump()
{
return "constexpr (" + this.value + ")";
}
compare(other)
{
return this.value(other.value);
}
isImmediate()
{
return true;
}
get variable()
{
return this._variable;
}
get value()
{
return this._value;
}
}
ConstExpr.mapping = {};
class ConstDecl extends Node
{
constructor(codeOrigin, variable, value)
{
super(codeOrigin);
this._variable = variable;
this._value = value;
}
children()
{
return [this.variable, this.value];
}
dump()
{
return "const " + this.variable.dump() + " = " + this.value.dump();
}
get variable()
{
return this._variable;
}
get value()
{
return this._value;
}
}
let _labelMapping = {};
let _referencedExternLabels = [];
class Label extends NoChildren
{
constructor(codeOrigin, name)
{
super(codeOrigin);
this._name = name;
this._extern = true;
this._global = false;
}
static forName(codeOrigin, name, definedInFile)
{
if (_labelMapping[name]) {
if (!_labelMapping[name] instanceof Label)
throw "Label name collision: " + name;
} else
_labelMapping[name] = new Label(codeOrigin, name);
if (definedInFile)
_labelMapping[name].clearExtern();
return _labelMapping[name];
}
static setAsGlobal(codeOrigin, name)
{
if (_labelMapping[name]) {
let label = _labelMapping[name];
if (label.isGlobal())
throw "Label: " + name + " declared global multiple times";
label.setGlobal();
} else {
let newLabel = new Label(codeOrigin, name);
newLabel.setGlobal();
_labelMapping[name] = newLabel;
}
}
static resetMappings()
{
_labelMapping = {};
_referencedExternLabels = [];
}
static resetReferenced()
{
_referencedExternLabels = [];
}
clearExtern()
{
this._extern = false;
}
isExtern()
{
return this._extern;
}
setGlobal()
{
this._global = true;
}
isGlobal()
{
return this._global;
}
dump()
{
return this.name + ":";
}
get name()
{
return this._name;
}
}
class LocalLabel extends NoChildren
{
constructor(codeOrigin, name)
{
super(codeOrigin);
this._name = name;
}
static forName(codeOrigin, name)
{
if (_labelMapping[name]) {
if (!_labelMapping[name] instanceof LocalLabel)
throw "Label name collision: " + name;
} else
_labelMapping[name] = new LocalLabel(codeOrigin, name);
return _labelMapping[name];
}
static unique(comment)
{
let newName = "_" + comment;
if (_labelMapping[newName]) {
while (_labelMapping[newName = "_#" + this.uniqueNameCounter + "_" + comment])
this.uniqueNameCounter++;
}
return forName(undefined, newName);
}
static resetMappings()
{
this.uniquNameCounter = 0;
}
cleanName()
{
if (/^\./.test(this._name))
return "_" + this._name.slice(1);
return this._name;
}
isGlobal()
{
return false;
}
dump()
{
return this.name + ":";
}
get name()
{
return this._name;
}
}
LocalLabel.uniqueNameCounter = 0;
class LabelReference extends Node
{
constructor(codeOrigin, label)
{
super(codeOrigin);
this._label = label;
}
children()
{
return [this.label];
}
name()
{
return this.label.name;
}
isExtern()
{
return _labelMapping[name] instanceof Label && _labelMapping[name].isExtern();
}
used()
{
if (!_referencedExternLabels.include(this._label) && this.isExtern())
_referencedExternLabels.push(this._label);
}
dump()
{
return this.label.name;
}
value()
{
return asmLabel();
}
get label()
{
return this._label;
}
get isAddress()
{
return false;
}
get isLabel()
{
return true;
}
get isImmediate()
{
return false;
}
get isImmediateOperand()
{
return true;
}
}
class LocalLabelReference extends NoChildren
{
constructor(codeOrigin, label)
{
super(codeOrigin);
this._label = label;
}
children()
{
return [this._label];
}
name()
{
return this.label.name;
}
dump()
{
return this.label.name;
}
value()
{
return asmLabel();
}
get label()
{
return this._label;
}
get isAddress()
{
return false;
}
get isLabel()
{
return true;
}
get isImmediate()
{
return false;
}
get isImmediateOperand()
{
return true;
}
}
class Sequence extends Node
{
constructor(codeOrigin, list)
{
super(codeOrigin);
this._list = list;
}
children()
{
return this.list;
}
dump()
{
return "" + this.list.map(function(v) { return v.dump()}).join("\n");
}
get list()
{
return this._list;
}
}
class True extends NoChildren
{
constructor()
{
super(undefined);
}
static instance()
{
return True.instance;
}
value()
{
return true;
}
dump()
{
return "true";
}
}
True.instance = new True();
class False extends NoChildren
{
constructor()
{
super(undefined);
}
static instance()
{
return False.instance;
}
value()
{
return false;
}
dump()
{
return "false";
}
}
False.instance = new False();
class Setting extends NoChildren
{
constructor(codeOrigin, name)
{
super(codeOrigin);
this._name = name;
}
static forName(codeOrigin, name)
{
if (!this.mapping[name])
this.mapping[name] = new Setting(codeOrigin, name);
return this.mapping[name];
}
static resetMappings()
{
this.mapping = {};
}
dump()
{
return this.name;
}
get name()
{
return this._name;
}
}
Setting.mapping = {};
class And extends Node
{
constructor(codeOrigin, left, right)
{
super(codeOrigin);
this._left = left;
this._right = right;
}
children()
{
return [this.left, this.right];
}
dump()
{
return "(" + this.left.dump() + " and " + this.right.dump() + ")";
}
get left()
{
return this._left;
}
get right()
{
return this._right;
}
}
class Or extends Node
{
constructor(codeOrigin, left, right)
{
super(codeOrigin);
this._left = left;
this._right = right;
}
children()
{
return [this.left, this.right];
}
dump()
{
return "(" + this.left.dump() + " or " + this.right.dump() + ")";
}
get left()
{
return this._left;
}
get right()
{
return this._right;
}
}
class Not extends Node
{
constructor(codeOrigin, child)
{
super(codeOrigin);
this._child = child;
}
children()
{
return [this.child];
}
dump()
{
return "(not" + this.child.dump() + ")";
}
get child()
{
return this._child;
}
}
class Skip extends NoChildren
{
constructor(codeOrigin)
{
super(codeOrigin);
}
dump()
{
return " skip";
}
}
class IfThenElse extends Node
{
constructor(codeOrigin, predicate, thenCase)
{
super(codeOrigin);
this._predicate = predicate;
this._thenCase = thenCase;
this._elseCase = new Skip(codeOrigin);
}
children()
{
return [];
}
dump()
{
return "if " + this.predicate.dump() + "\n" + this.thenCase.dump() + "\nelse\n" + this.elseCase.dump() + "\nend";
}
get predicate()
{
return this._predicate;
}
get thenCase()
{
return this._thenCase;
}
get elseCase()
{
return this._elseCase;
}
set elseCase(newElseCase)
{
this._elseCase = newElseCase;
}
}
class Macro extends Node
{
constructor(codeOrigin, name, variables, body)
{
super(codeOrigin);
this._name = name;
this._variables = variables;
this._body = body;
}
children()
{
return [];
}
dump()
{
return "macro " + this.name + "(" + this.variables.map(function(v) { return v.dump()}).join(", ") + ")\n" + this.body.dump() + "\nend";
}
get name()
{
return this._name;
}
get variables()
{
return this._variables;
}
get body()
{
return this._body;
}
}
class MacroCall extends Node
{
constructor(codeOrigin, name, operands, annotation)
{
super(codeOrigin);
this._name = name;
this._operands = operands;
if (!this._operands)
throw "Operands empty to Macro call " + name;
this._annotation = annotation;
}
children()
{
return [];
}
dump()
{
return " " + this.name + "(" + this.operands.map(function(v) { return v.dump() }).join(", ") + ")";
}
get name()
{
return this._name;
}
get operands()
{
return this._operands;
}
get annotation()
{
return this._annotation;
}
}
function resetAST()
{
StructOffset.resetMappings();
Sizeof.resetMappings();
ConstExpr.resetMappings();
Label.resetMappings();
LocalLabel.resetMappings();
Setting.resetMappings();
}