// Copyright 2019 Google LLC // // 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. import { assert } from "chai"; import Lexer from "./lexer"; import Parser from "./parser"; import grammar from "./spirv.data.js"; describe("parser", () => { it("parses an opcode", () => { let input = "OpKill"; let l = new Lexer(input); let p = new Parser(grammar, l); let ast = p.parse(); assert.exists(ast); assert.lengthOf(ast.instructions(), 1); let inst = ast.instruction(0); assert.equal(inst.name(), "OpKill"); assert.equal(inst.opcode(), 252); assert.lengthOf(inst.operands, 0); }); it("parses an opcode with an identifier", () => { let input = "OpCapability Shader"; let l = new Lexer(input); let p = new Parser(grammar, l); let ast = p.parse(); assert.exists(ast, p.error); assert.lengthOf(ast.instructions(), 1); let inst = ast.instruction(0); assert.equal(inst.name(), "OpCapability"); assert.equal(inst.opcode(), 17); assert.lengthOf(inst.operands(), 1); let op = inst.operand(0); assert.equal(op.name(), "Shader"); assert.equal(op.type(), "ValueEnum"); assert.equal(op.value(), 1); }); it("parses an opcode with a result", () => { let input = "%void = OpTypeVoid"; let l = new Lexer(input); let p = new Parser(grammar, l); let ast = p.parse(); assert.exists(ast); assert.lengthOf(ast.instructions(), 1); let inst = ast.instruction(0); assert.equal(inst.name(), "OpTypeVoid"); assert.equal(inst.opcode(), 19); assert.lengthOf(inst.operands(), 1); let op = inst.operand(0); assert.equal(op.name(), "void"); assert.equal(op.value(), 1); }); it("sets module bounds based on numeric result", () => { let input = "%3 = OpTypeVoid"; let l = new Lexer(input); let p = new Parser(grammar, l); let ast = p.parse(); assert.exists(ast); assert.equal(ast.getId("next"), 4); }); it("returns the same value for a named result_id", () => { let input = "%3 = OpTypeFunction %int %int"; let l = new Lexer(input); let p = new Parser(grammar, l); let ast = p.parse(); assert.exists(ast); assert.lengthOf(ast.instructions(), 1); let inst = ast.instruction(0); let op1 = inst.operand(1); assert.equal(op1.name(), "int"); assert.equal(op1.value(), 4); let op2 = inst.operand(2); assert.equal(op2.name(), "int"); assert.equal(op2.value(), 4); }); it("parses an opcode with a string", () => { let input = "OpEntryPoint Fragment %main \"main\""; let l = new Lexer(input); let p = new Parser(grammar, l); let ast = p.parse(); assert.exists(ast); assert.lengthOf(ast.instructions(), 1); let inst = ast.instruction(0); let op = inst.operand(2); assert.equal(op.name(), "main"); assert.equal(op.value(), "main"); }); describe("numerics", () => { describe("integers", () => { it("parses an opcode with an integer", () => { let input = "OpSource GLSL 440"; let l = new Lexer(input); let p = new Parser(grammar, l); let ast = p.parse(); assert.exists(ast); assert.lengthOf(ast.instructions(), 1); let inst = ast.instruction(0); let op0 = inst.operand(0); assert.equal(op0.name(), "GLSL"); assert.equal(op0.type(), "ValueEnum"); assert.equal(op0.value(), 2); let op1 = inst.operand(1); assert.equal(op1.name(), "440"); assert.equal(op1.value(), 440); }); it("parses an opcode with a hex integer", () => { let input = "OpSource GLSL 0x440"; let l = new Lexer(input); let p = new Parser(grammar, l); let ast = p.parse(); assert.exists(ast); assert.lengthOf(ast.instructions(), 1); let inst = ast.instruction(0); let op0 = inst.operand(0); assert.equal(op0.name(), "GLSL"); assert.equal(op0.type(), "ValueEnum"); assert.equal(op0.value(), 2); let op1 = inst.operand(1); assert.equal(op1.name(), "1088"); assert.equal(op1.value(), 0x440); }); it.skip("parses immediate integers", () => { // TODO(dsinclair): Support or skip? }); }); describe("floats", () => { it("parses floats", () => { let input = `%float = OpTypeFloat 32 %float1 = OpConstant %float 0.400000006`; let l = new Lexer(input); let p = new Parser(grammar, l); let ast = p.parse(); assert.exists(ast, p.error); assert.lengthOf(ast.instructions(), 2); let inst = ast.instruction(1); let op2 = inst.operand(2); assert.equal(op2.value(), 0.400000006); }); // TODO(dsinclair): Make hex encoded floats parse ... it.skip("parses hex floats", () => { let input = `%float = OpTypeFloat 32 %nfloat = OpConstant %float -0.4p+2 %pfloat = OpConstant %float 0.4p-2 %inf = OpConstant %float32 0x1p+128 %neginf = OpConstant %float32 -0x1p+128 %aNaN = OpConstant %float32 0x1.8p+128 %moreNaN = OpConstant %float32 -0x1.0002p+128`; let results = [-40.0, .004, 0x00000, 0x00000, 0x7fc00000, 0xff800100]; let l = new Lexer(input); let p = new Parser(grammar, l); let ast = p.parse(); assert.exists(ast, p.error); assert.lengthOf(ast.instructions(), 7); for (const idx in results) { let inst = ast.instruction(idx); let op2 = inst.operand(2); assert.equal(op2.value(), results[idx]); } }); it("parses a float that looks like an int", () => { let input = `%float = OpTypeFloat 32 %float1 = OpConstant %float 1`; let l = new Lexer(input); let p = new Parser(grammar, l); let ast = p.parse(); assert.exists(ast, p.error); assert.lengthOf(ast.instructions(), 2); let inst = ast.instruction(1); let op2 = inst.operand(2); assert.equal(op2.value(), 1); assert.equal(op2.type(), "float"); }); }); }); describe("enums", () => { it("parses enum values", () => { let input = `%1 = OpTypeFloat 32 %30 = OpImageSampleExplicitLod %1 %20 %18 Grad|ConstOffset %22 %24 %29`; let vals = [{val: 1, name: "1"}, {val: 30, name: "30"}, {val: 20, name: "20"}, {val: 18, name: "18"}, {val: 12, name: "Grad|ConstOffset"}]; let l = new Lexer(input); let p = new Parser(grammar, l); let ast = p.parse(); assert.exists(ast, p.error); assert.lengthOf(ast.instructions(), 2); let inst = ast.instruction(1); for (let idx in vals) { let op = inst.operand(idx); assert.equal(op.name(), vals[idx].name); assert.equal(op.value(), vals[idx].val); } // BitEnum let params = inst.operand(4).params(); assert.lengthOf(params, 3); assert.equal(params[0].name(), "22"); assert.equal(params[0].value(), 22); assert.equal(params[1].name(), "24"); assert.equal(params[1].value(), 24); assert.equal(params[2].name(), "29"); assert.equal(params[2].value(), 29); }); it("parses enumerants with parameters", () => { let input ="OpExecutionMode %main LocalSize 2 3 4"; let l = new Lexer(input); let p = new Parser(grammar, l); let ast = p.parse(); assert.exists(ast, p.error); assert.lengthOf(ast.instructions(), 1); let inst = ast.instruction(0); assert.equal(inst.name(), "OpExecutionMode"); assert.lengthOf(inst.operands(), 2); assert.equal(inst.operand(0).name(), "main"); assert.equal(inst.operand(1).name(), "LocalSize"); let params = inst.operand(1).params(); assert.lengthOf(params, 3); assert.equal(params[0].name(), "2"); assert.equal(params[1].name(), "3"); assert.equal(params[2].name(), "4"); }); }); it("parses result into second operand if needed", () => { let input = `%int = OpTypeInt 32 1 %int_3 = OpConstant %int 3`; let l = new Lexer(input); let p = new Parser(grammar, l); let ast = p.parse(); assert.exists(ast); assert.lengthOf(ast.instructions(), 2); let inst = ast.instruction(1); assert.equal(inst.name(), "OpConstant"); assert.equal(inst.opcode(), 43); assert.lengthOf(inst.operands(), 3); let op0 = inst.operand(0); assert.equal(op0.name(), "int"); assert.equal(op0.value(), 1); let op1 = inst.operand(1); assert.equal(op1.name(), "int_3"); assert.equal(op1.value(), 2); let op2 = inst.operand(2); assert.equal(op2.name(), "3"); assert.equal(op2.value(), 3); }); describe("quantifiers", () => { describe("?", () => { it("skips if missing", () => { let input = `OpImageWrite %1 %2 %3 OpKill`; let l = new Lexer(input); let p = new Parser(grammar, l); let ast = p.parse(); assert.exists(ast); assert.lengthOf(ast.instructions(), 2); let inst = ast.instruction(0); assert.equal(inst.name(), "OpImageWrite"); assert.lengthOf(inst.operands(), 3); }); it("skips if missing at EOF", () => { let input = "OpImageWrite %1 %2 %3"; let l = new Lexer(input); let p = new Parser(grammar, l); let ast = p.parse(); assert.exists(ast); assert.lengthOf(ast.instructions(), 1); let inst = ast.instruction(0); assert.equal(inst.name(), "OpImageWrite"); assert.lengthOf(inst.operands(), 3); }); it("extracts if available", () => { let input = `OpImageWrite %1 %2 %3 ConstOffset %2 OpKill`; let l = new Lexer(input); let p = new Parser(grammar, l); let ast = p.parse(); assert.exists(ast); assert.lengthOf(ast.instructions(), 2); let inst = ast.instruction(0); assert.equal(inst.name(), "OpImageWrite"); assert.lengthOf(inst.operands(), 4); assert.equal(inst.operand(3).name(), "ConstOffset"); }); }); describe("*", () => { it("skips if missing", () => { let input = `OpEntryPoint Fragment %main "main" OpKill`; let l = new Lexer(input); let p = new Parser(grammar, l); let ast = p.parse(); assert.exists(ast); assert.lengthOf(ast.instructions(), 2); let inst = ast.instruction(0); assert.equal(inst.name(), "OpEntryPoint"); assert.lengthOf(inst.operands(), 3); assert.equal(inst.operand(2).name(), "main"); }); it("extracts one if available", () => { let input = `OpEntryPoint Fragment %main "main" %2 OpKill`; let l = new Lexer(input); let p = new Parser(grammar, l); let ast = p.parse(); assert.exists(ast); assert.lengthOf(ast.instructions(), 2); let inst = ast.instruction(0); assert.equal(inst.name(), "OpEntryPoint"); assert.lengthOf(inst.operands(), 4); assert.equal(inst.operand(3).name(), "2"); }); it("extracts multiple if available", () => { let input = `OpEntryPoint Fragment %main "main" %2 %3 %4 %5 OpKill`; let l = new Lexer(input); let p = new Parser(grammar, l); let ast = p.parse(); assert.exists(ast); assert.lengthOf(ast.instructions(), 2); let inst = ast.instruction(0); assert.equal(inst.name(), "OpEntryPoint"); assert.lengthOf(inst.operands(), 7); assert.equal(inst.operand(3).name(), "2"); assert.equal(inst.operand(4).name(), "3"); assert.equal(inst.operand(5).name(), "4"); assert.equal(inst.operand(6).name(), "5"); }); }); }); describe("extended instructions", () => { it("errors on non-glsl extensions", () => { let input = "%1 = OpExtInstImport \"OpenCL.std.100\""; let l = new Lexer(input); let p = new Parser(grammar, l); assert.isUndefined(p.parse()); }); it("handles extended instructions", () => { let input = `%1 = OpExtInstImport "GLSL.std.450" %44 = OpExtInst %7 %1 Sqrt %43`; let l = new Lexer(input); let p = new Parser(grammar, l); let ast = p.parse(); assert.exists(ast, p.error); assert.lengthOf(ast.instructions(), 2); let inst = ast.instruction(1); assert.lengthOf(inst.operands(), 5); assert.equal(inst.operand(3).value(), 31); assert.equal(inst.operand(3).name(), "Sqrt"); assert.equal(inst.operand(4).value(), 43); assert.equal(inst.operand(4).name(), "43"); }); }); it.skip("handles spec constant ops", () => { // let input = "%sum = OpSpecConstantOp %i32 IAdd %a %b"; }); it("handles OpCopyMemory", () => { let input = "OpCopyMemory %1 %2 " + "Volatile|Nontemporal|MakePointerVisible %3 " + "Aligned|MakePointerAvailable|NonPrivatePointer 16 %4"; let l = new Lexer(input); let p = new Parser(grammar, l); let ast = p.parse(); assert.exists(ast, p.error); assert.lengthOf(ast.instructions(), 1); let inst = ast.instruction(0); assert.lengthOf(inst.operands(), 4); assert.equal(inst.operand(0).value(), 1); assert.equal(inst.operand(1).value(), 2); assert.equal(inst.operand(2).name(), "Volatile|Nontemporal|MakePointerVisible"); assert.equal(inst.operand(2).value(), 21); assert.lengthOf(inst.operand(2).params(), 1); assert.equal(inst.operand(2).params()[0].value(), 3); assert.equal(inst.operand(3).name(), "Aligned|MakePointerAvailable|NonPrivatePointer"); assert.equal(inst.operand(3).value(), 42); assert.lengthOf(inst.operand(3).params(), 2); assert.equal(inst.operand(3).params()[0].value(), 16); assert.equal(inst.operand(3).params()[1].value(), 4); }); });