blob: 80ca7044ab066eb831466cbf023b694fb78e8ea2 [file] [log] [blame]
/*
* Copyright (C) 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";
// This function accepts a JSON object describing the SPIR-V syntax.
// For example, https://github.com/KhronosGroup/SPIRV-Headers/blob/master/include/spirv/1.2/spirv.core.grammar.json
function SPIRV(json) {
let result = {
ops: {},
kinds: {}
}
let composites = new Map();
let ids = new Map();
for (let kind of json.operand_kinds) {
switch (kind.category) {
case "BitEnum":
case "ValueEnum":
let enumerants = { category: kind.category };
for (let enumerant of kind.enumerants) {
enumerants[enumerant.enumerant] = enumerant;
}
result.kinds[kind.kind] = enumerants;
break;
case "Composite":
composites.set(kind.kind, kind);
break;
case "Id":
ids.set(kind.kind, kind);
break;
}
}
function matchType(operandInfoKind, operand) {
switch (operandInfoKind) {
// FIXME: I'm not actually sure that Ids should be unsigned.
case "IdResultType":
case "IdResult":
case "IdRef":
case "IdScope":
case "IdMemorySemantics":
case "LiteralExtInstInteger":
if (typeof operand != "number")
throw new Error("Operand needs to be a number");
if ((operand >>> 0) != operand)
throw new Error("Operand needs to fit in an unsigned int");
return;
case "LiteralInteger":
if (typeof operand != "number")
throw new Error("Operand needs to be a number");
if ((operand | 0) != operand)
throw new Error("Operand needs to fit in an int");
return;
case "LiteralString":
if (typeof operand != "string")
throw new Error("Operand needs to be a string");
return;
case "LiteralContextDependentNumber":
case "LiteralSpecConstantOpInteger":
if (typeof operand != "number")
throw new Error("Operand needs to be a number");
if ((operand >>> 0) != operand && (operand | 0) != operand)
throw new Error("Operand needs to fit in an unsigned int or an int.");
return;
}
let kind = result.kinds[operandInfoKind];
if (kind) {
if (operand instanceof Array) {
if (kind.category != "BitEnum")
throw new Error("Passing an array to a " + kind.category + " operand");
for (let operandItem of operand) {
if (kind[operandItem.enumerant] != operandItem)
throw new Error("" + operandItem.enumerant + " is not a member of " + operandInfoKind);
}
return;
}
if (kind[operand.enumerant] != operand)
throw new Error("" + operand.enumerant + " is not a member of " + operandInfoKind);
return;
}
throw new Error("Unknown type: " + operandInfoKind);
}
class OperandChecker {
constructor(operandInfos)
{
this._operandInfos = operandInfos || [];
this._operandIndex = 0;
this._operandInfoIndex = 0;
this._parameters = [];
}
_isStar(operandInfo)
{
switch (operandInfo.kind) {
case "LiteralContextDependentNumber":
case "LiteralSpecConstantOpInteger":
// These types can be any width.
return true;
}
return operandInfo.quantifier && operandInfo.quantifier == "*";
}
nextComparisonType(operand)
{
if (this._operandInfoIndex >= this._operandInfos.length)
throw new Error("Specified operand does not correspond to any that the instruction expects.");
let operandInfo = this._operandInfos[this._operandInfoIndex];
let isStar = this._isStar(operandInfo);
if (this._parameters.length != 0) {
let result = this._parameters[0];
this._parameters.splice(0, 1);
// FIXME: Handle parameters that require their own parameters
++this._operandIndex;
if (this._parameters.length == 0 && !isStar)
++this._operandInfoIndex;
return result;
}
let composite = composites.get(operandInfo.kind);
if (composite) {
for (let base of composite.bases)
this._parameters.push(base);
nextComparisonType(operand);
return;
}
let kind = result.kinds[operandInfo.kind];
if (kind) {
let enumerant = kind[operand.enumerant];
if (enumerant) {
let parameters = enumerant.parameters;
if (parameters) {
for (let parameter of parameters) {
this._parameters.push(parameter.kind);
}
++this._operandIndex;
return operandInfo.kind;
}
}
}
++this._operandIndex;
if (!isStar)
++this._operandInfoIndex;
return operandInfo.kind;
}
check(operand)
{
matchType(this.nextComparisonType(operand), operand);
}
finalize()
{
if (this._parameters.length != 0)
throw new Error("Operand not specified for parameter.");
for (let i = this._operandInfoIndex; i < this._operandInfos.length; ++i) {
let operandInfo = this._operandInfos[i];
let quantifier = operandInfo.quantifier;
if (quantifier != "?" && !this._isStar(operandInfo))
throw new Error("Did not specify operand " + i + " to instruction.");
}
}
}
for (let instruction of json.instructions) {
if (!instruction.opname.startsWith("Op"))
continue;
let attributeName = instruction.opname.substring(2);
result.ops[attributeName] = class {
constructor(...operands)
{
let operandChecker = new OperandChecker(instruction.operands);
for (let operand of operands)
operandChecker.check(operand);
operandChecker.finalize();
this._operands = operands;
}
get operands()
{
return this._operands;
}
get opname()
{
return instruction.opname;
}
get opcode()
{
return instruction.opcode;
}
get operandInfo()
{
return instruction.operands;
}
get storageSize()
{
let result = 1;
for (let operand of this.operands) {
if (typeof operand == "number")
++result;
else if (typeof operand == "string")
result += (((operand.length + 1) + 3) / 4) | 0;
else
++result;
}
return result;
}
get largestId()
{
let maximumId = 0;
let operandChecker = new OperandChecker(this.operandInfo);
for (let operand of this.operands) {
let type = operandChecker.nextComparisonType(operand);
let idType = ids.get(type);
if (idType)
maximumId = Math.max(maximumId, operand);
}
return maximumId;
}
}
}
return result;
}
class SPIRVAssembler {
constructor()
{
this._largestId = 0;
this._size = 5;
this._storage = new Uint32Array(this.size);
this.storage[0] = 0x07230203; // Magic number
this.storage[1] = 0x00010000; // Version: 1.0
this.storage[2] = 0x574B0000; // Tool: "WK"
this.storage[3] = 0; // Placeholder: All <id>s are less than this value.
this.storage[4] = 0; // Reserved
}
append(op)
{
this._largestId = Math.max(this._largestId, op.largestId);
let deltaStorageSize = op.storageSize;
if (this.size + deltaStorageSize > this.storage.length) {
let newStorageSize = (this.size + deltaStorageSize) * 1.5;
let newStorage = new Uint32Array(newStorageSize);
for (let i = 0; i < this.size; ++i)
newStorage[i] = this.storage[i];
this._storage = newStorage;
}
if ((deltaStorageSize & 0xFFFF) != deltaStorageSize || (op.opcode & 0xFFFF) != op.opcode)
throw new Error("Out of bounds!");
this.storage[this.size] = ((deltaStorageSize & 0xFFFF) << 16) | (op.opcode & 0xFFFF);
++this._size;
if (deltaStorageSize <= op.operands.size)
throw new Error("The storage size must be greater than the number of parameters");
for (let i = 0; i < op.operands.length; ++i) {
let operand = op.operands[i];
if (typeof operand == "number") {
this.storage[this.size] = operand;
++this._size;
} else if (typeof operand == "string") {
let word = 0;
for (let j = 0; j < operand.length + 1; ++j) {
let charCode;
if (j < operand.length)
charCode = operand.charCodeAt(j);
else
charCode = 0;
if (charCode > 0xFF)
throw new Error("Non-ASCII strings don't work yet");
switch (j % 4) {
case 0:
word |= charCode;
break;
case 1:
word |= (charCode << 8);
break;
case 2:
word |= (charCode << 16);
break;
case 3:
word |= (charCode << 24);
this.storage[this.size + ((j / 4) | 0)] = word;
word = 0;
break;
}
}
if ((operand.length % 4) != 0)
this.storage[this.size + (((operand.length + 1) / 4) | 0)] = word;
this._size += (((operand.length + 1) + 3) / 4) | 0;
} else {
this.storage[this.size] = operand.value;
++this._size;
}
}
}
get size()
{
return this._size;
}
get storage()
{
return this._storage;
}
get result()
{
this.storage[3] = this._largestId + 1;
return this.storage.slice(0, this.size);
}
}