| // 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); |
| }); |
| }); |