1// Copyright 2019 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import { assert } from "chai";
16import Lexer from "./lexer";
17import Parser from "./parser";
18import grammar from "./spirv.data.js";
19
20describe("parser", () => {
21  it("parses an opcode", () => {
22    let input = "OpKill";
23    let l = new Lexer(input);
24    let p = new Parser(grammar, l);
25
26    let ast = p.parse();
27    assert.exists(ast);
28    assert.lengthOf(ast.instructions(), 1);
29
30    let inst = ast.instruction(0);
31    assert.equal(inst.name(), "OpKill");
32    assert.equal(inst.opcode(), 252);
33    assert.lengthOf(inst.operands, 0);
34  });
35
36  it("parses an opcode with an identifier", () => {
37    let input = "OpCapability Shader";
38    let l = new Lexer(input);
39    let p = new Parser(grammar, l);
40
41    let ast = p.parse();
42    assert.exists(ast, p.error);
43    assert.lengthOf(ast.instructions(), 1);
44
45    let inst = ast.instruction(0);
46    assert.equal(inst.name(), "OpCapability");
47    assert.equal(inst.opcode(), 17);
48    assert.lengthOf(inst.operands(), 1);
49
50    let op = inst.operand(0);
51    assert.equal(op.name(), "Shader");
52    assert.equal(op.type(), "ValueEnum");
53    assert.equal(op.value(), 1);
54  });
55
56  it("parses an opcode with a result", () => {
57    let input = "%void = OpTypeVoid";
58    let l = new Lexer(input);
59    let p = new Parser(grammar, l);
60
61    let ast = p.parse();
62    assert.exists(ast);
63    assert.lengthOf(ast.instructions(), 1);
64
65    let inst = ast.instruction(0);
66    assert.equal(inst.name(), "OpTypeVoid");
67    assert.equal(inst.opcode(), 19);
68    assert.lengthOf(inst.operands(), 1);
69
70    let op = inst.operand(0);
71    assert.equal(op.name(), "void");
72    assert.equal(op.value(), 1);
73  });
74
75  it("sets module bounds based on numeric result", () => {
76    let input = "%3 = OpTypeVoid";
77
78    let l = new Lexer(input);
79    let p = new Parser(grammar, l);
80
81    let ast = p.parse();
82    assert.exists(ast);
83    assert.equal(ast.getId("next"), 4);
84  });
85
86  it("returns the same value for a named result_id", () => {
87    let input = "%3 = OpTypeFunction %int %int";
88
89    let l = new Lexer(input);
90    let p = new Parser(grammar, l);
91
92    let ast = p.parse();
93    assert.exists(ast);
94    assert.lengthOf(ast.instructions(), 1);
95
96    let inst = ast.instruction(0);
97    let op1 = inst.operand(1);
98    assert.equal(op1.name(), "int");
99    assert.equal(op1.value(), 4);
100
101    let op2 = inst.operand(2);
102    assert.equal(op2.name(), "int");
103    assert.equal(op2.value(), 4);
104  });
105
106  it("parses an opcode with a string", () => {
107    let input = "OpEntryPoint Fragment %main \"main\"";
108
109    let l = new Lexer(input);
110    let p = new Parser(grammar, l);
111
112    let ast = p.parse();
113    assert.exists(ast);
114    assert.lengthOf(ast.instructions(), 1);
115
116    let inst = ast.instruction(0);
117    let op = inst.operand(2);
118    assert.equal(op.name(), "main");
119    assert.equal(op.value(), "main");
120  });
121
122  describe("numerics", () => {
123    describe("integers", () => {
124      it("parses an opcode with an integer", () => {
125        let input = "OpSource GLSL 440";
126
127        let l = new Lexer(input);
128        let p = new Parser(grammar, l);
129
130        let ast = p.parse();
131        assert.exists(ast);
132        assert.lengthOf(ast.instructions(), 1);
133
134        let inst = ast.instruction(0);
135        let op0 = inst.operand(0);
136        assert.equal(op0.name(), "GLSL");
137        assert.equal(op0.type(), "ValueEnum");
138        assert.equal(op0.value(), 2);
139
140        let op1 = inst.operand(1);
141        assert.equal(op1.name(), "440");
142        assert.equal(op1.value(), 440);
143      });
144
145      it("parses an opcode with a hex integer", () => {
146        let input = "OpSource GLSL 0x440";
147
148        let l = new Lexer(input);
149        let p = new Parser(grammar, l);
150
151        let ast = p.parse();
152        assert.exists(ast);
153        assert.lengthOf(ast.instructions(), 1);
154
155        let inst = ast.instruction(0);
156        let op0 = inst.operand(0);
157        assert.equal(op0.name(), "GLSL");
158        assert.equal(op0.type(), "ValueEnum");
159        assert.equal(op0.value(), 2);
160
161        let op1 = inst.operand(1);
162        assert.equal(op1.name(), "1088");
163        assert.equal(op1.value(), 0x440);
164      });
165
166      it.skip("parses immediate integers", () => {
167        // TODO(dsinclair): Support or skip?
168      });
169    });
170
171    describe("floats", () => {
172      it("parses floats", () => {
173        let input = `%float = OpTypeFloat 32
174                     %float1 = OpConstant %float 0.400000006`;
175
176        let l = new Lexer(input);
177        let p = new Parser(grammar, l);
178
179        let ast = p.parse();
180        assert.exists(ast, p.error);
181        assert.lengthOf(ast.instructions(), 2);
182
183        let inst = ast.instruction(1);
184        let op2 = inst.operand(2);
185        assert.equal(op2.value(), 0.400000006);
186      });
187
188      // TODO(dsinclair): Make hex encoded floats parse ...
189      it.skip("parses hex floats", () => {
190        let input = `%float = OpTypeFloat 32
191                     %nfloat = OpConstant %float -0.4p+2
192                     %pfloat = OpConstant %float 0.4p-2
193                     %inf = OpConstant %float32 0x1p+128
194                     %neginf = OpConstant %float32 -0x1p+128
195                     %aNaN = OpConstant %float32 0x1.8p+128
196                     %moreNaN = OpConstant %float32 -0x1.0002p+128`;
197
198        let results = [-40.0, .004, 0x00000, 0x00000, 0x7fc00000, 0xff800100];
199        let l = new Lexer(input);
200        let p = new Parser(grammar, l);
201
202        let ast = p.parse();
203        assert.exists(ast, p.error);
204        assert.lengthOf(ast.instructions(), 7);
205
206        for (const idx in results) {
207          let inst = ast.instruction(idx);
208          let op2 = inst.operand(2);
209          assert.equal(op2.value(), results[idx]);
210        }
211      });
212
213      it("parses a float that looks like an int", () => {
214        let input = `%float = OpTypeFloat 32
215                     %float1 = OpConstant %float 1`;
216
217        let l = new Lexer(input);
218        let p = new Parser(grammar, l);
219
220        let ast = p.parse();
221        assert.exists(ast, p.error);
222        assert.lengthOf(ast.instructions(), 2);
223
224        let inst = ast.instruction(1);
225        let op2 = inst.operand(2);
226        assert.equal(op2.value(), 1);
227        assert.equal(op2.type(), "float");
228      });
229    });
230  });
231
232  describe("enums", () => {
233    it("parses enum values", () => {
234      let input = `%1 = OpTypeFloat 32
235  %30 = OpImageSampleExplicitLod %1 %20 %18 Grad|ConstOffset %22 %24 %29`;
236
237      let vals = [{val: 1, name: "1"},
238        {val: 30, name: "30"},
239        {val: 20, name: "20"},
240        {val: 18, name: "18"},
241        {val: 12, name: "Grad|ConstOffset"}];
242
243      let l = new Lexer(input);
244      let p = new Parser(grammar, l);
245
246      let ast = p.parse();
247      assert.exists(ast, p.error);
248      assert.lengthOf(ast.instructions(), 2);
249
250      let inst = ast.instruction(1);
251      for (let idx in vals) {
252        let op = inst.operand(idx);
253        assert.equal(op.name(), vals[idx].name);
254        assert.equal(op.value(), vals[idx].val);
255      }
256
257      // BitEnum
258      let params = inst.operand(4).params();
259      assert.lengthOf(params, 3);
260      assert.equal(params[0].name(), "22");
261      assert.equal(params[0].value(), 22);
262      assert.equal(params[1].name(), "24");
263      assert.equal(params[1].value(), 24);
264      assert.equal(params[2].name(), "29");
265      assert.equal(params[2].value(), 29);
266    });
267
268    it("parses enumerants with parameters", () => {
269      let input ="OpExecutionMode %main LocalSize 2 3 4";
270
271      let l = new Lexer(input);
272      let p = new Parser(grammar, l);
273
274      let ast = p.parse();
275      assert.exists(ast, p.error);
276      assert.lengthOf(ast.instructions(), 1);
277
278      let inst = ast.instruction(0);
279      assert.equal(inst.name(), "OpExecutionMode");
280      assert.lengthOf(inst.operands(), 2);
281      assert.equal(inst.operand(0).name(), "main");
282      assert.equal(inst.operand(1).name(), "LocalSize");
283
284      let params = inst.operand(1).params();
285      assert.lengthOf(params, 3);
286      assert.equal(params[0].name(), "2");
287      assert.equal(params[1].name(), "3");
288      assert.equal(params[2].name(), "4");
289    });
290  });
291
292  it("parses result into second operand if needed", () => {
293    let input = `%int = OpTypeInt 32 1
294                 %int_3 = OpConstant %int 3`;
295    let l = new Lexer(input);
296    let p = new Parser(grammar, l);
297
298    let ast = p.parse();
299    assert.exists(ast);
300    assert.lengthOf(ast.instructions(), 2);
301
302    let inst = ast.instruction(1);
303    assert.equal(inst.name(), "OpConstant");
304    assert.equal(inst.opcode(), 43);
305    assert.lengthOf(inst.operands(), 3);
306
307    let op0 = inst.operand(0);
308    assert.equal(op0.name(), "int");
309    assert.equal(op0.value(), 1);
310
311    let op1 = inst.operand(1);
312    assert.equal(op1.name(), "int_3");
313    assert.equal(op1.value(), 2);
314
315    let op2 = inst.operand(2);
316    assert.equal(op2.name(), "3");
317    assert.equal(op2.value(), 3);
318  });
319
320  describe("quantifiers", () => {
321    describe("?", () => {
322      it("skips if missing", () => {
323        let input = `OpImageWrite %1 %2 %3
324OpKill`;
325        let l = new Lexer(input);
326        let p = new Parser(grammar, l);
327
328        let ast = p.parse();
329        assert.exists(ast);
330        assert.lengthOf(ast.instructions(), 2);
331
332        let inst = ast.instruction(0);
333        assert.equal(inst.name(), "OpImageWrite");
334        assert.lengthOf(inst.operands(), 3);
335      });
336
337      it("skips if missing at EOF", () => {
338        let input = "OpImageWrite %1 %2 %3";
339        let l = new Lexer(input);
340        let p = new Parser(grammar, l);
341
342        let ast = p.parse();
343        assert.exists(ast);
344        assert.lengthOf(ast.instructions(), 1);
345
346        let inst = ast.instruction(0);
347        assert.equal(inst.name(), "OpImageWrite");
348        assert.lengthOf(inst.operands(), 3);
349      });
350
351      it("extracts if available", () => {
352        let input = `OpImageWrite %1 %2 %3 ConstOffset %2
353OpKill`;
354        let l = new Lexer(input);
355        let p = new Parser(grammar, l);
356
357        let ast = p.parse();
358        assert.exists(ast);
359        assert.lengthOf(ast.instructions(), 2);
360
361        let inst = ast.instruction(0);
362        assert.equal(inst.name(), "OpImageWrite");
363        assert.lengthOf(inst.operands(), 4);
364        assert.equal(inst.operand(3).name(), "ConstOffset");
365      });
366    });
367
368    describe("*", () => {
369      it("skips if missing", () => {
370        let input = `OpEntryPoint Fragment %main "main"
371OpKill`;
372
373        let l = new Lexer(input);
374        let p = new Parser(grammar, l);
375
376        let ast = p.parse();
377        assert.exists(ast);
378        assert.lengthOf(ast.instructions(), 2);
379
380        let inst = ast.instruction(0);
381        assert.equal(inst.name(), "OpEntryPoint");
382        assert.lengthOf(inst.operands(), 3);
383        assert.equal(inst.operand(2).name(), "main");
384      });
385
386      it("extracts one if available", () => {
387        let input = `OpEntryPoint Fragment %main "main" %2
388OpKill`;
389
390        let l = new Lexer(input);
391        let p = new Parser(grammar, l);
392
393        let ast = p.parse();
394        assert.exists(ast);
395        assert.lengthOf(ast.instructions(), 2);
396
397        let inst = ast.instruction(0);
398        assert.equal(inst.name(), "OpEntryPoint");
399        assert.lengthOf(inst.operands(), 4);
400        assert.equal(inst.operand(3).name(), "2");
401      });
402
403      it("extracts multiple if available", () => {
404        let input = `OpEntryPoint Fragment %main "main" %2 %3 %4 %5
405OpKill`;
406
407        let l = new Lexer(input);
408        let p = new Parser(grammar, l);
409
410        let ast = p.parse();
411        assert.exists(ast);
412        assert.lengthOf(ast.instructions(), 2);
413
414        let inst = ast.instruction(0);
415        assert.equal(inst.name(), "OpEntryPoint");
416        assert.lengthOf(inst.operands(), 7);
417        assert.equal(inst.operand(3).name(), "2");
418        assert.equal(inst.operand(4).name(), "3");
419        assert.equal(inst.operand(5).name(), "4");
420        assert.equal(inst.operand(6).name(), "5");
421      });
422    });
423  });
424
425  describe("extended instructions", () => {
426    it("errors on non-glsl extensions", () => {
427      let input = "%1 = OpExtInstImport \"OpenCL.std.100\"";
428
429      let l = new Lexer(input);
430      let p = new Parser(grammar, l);
431
432      assert.isUndefined(p.parse());
433    });
434
435    it("handles extended instructions", () => {
436      let input = `%1 = OpExtInstImport "GLSL.std.450"
437  %44 = OpExtInst %7 %1 Sqrt %43`;
438
439      let l = new Lexer(input);
440      let p = new Parser(grammar, l);
441
442      let ast = p.parse();
443      assert.exists(ast, p.error);
444      assert.lengthOf(ast.instructions(), 2);
445
446      let inst = ast.instruction(1);
447      assert.lengthOf(inst.operands(), 5);
448      assert.equal(inst.operand(3).value(), 31);
449      assert.equal(inst.operand(3).name(), "Sqrt");
450      assert.equal(inst.operand(4).value(), 43);
451      assert.equal(inst.operand(4).name(), "43");
452    });
453  });
454
455  it.skip("handles spec constant ops", () => {
456    // let input = "%sum = OpSpecConstantOp %i32 IAdd %a %b";
457  });
458
459  it("handles OpCopyMemory", () => {
460    let input = "OpCopyMemory %1 %2 " +
461                "Volatile|Nontemporal|MakePointerVisible %3 " +
462                "Aligned|MakePointerAvailable|NonPrivatePointer 16 %4";
463
464    let l = new Lexer(input);
465    let p = new Parser(grammar, l);
466
467    let ast = p.parse();
468    assert.exists(ast, p.error);
469    assert.lengthOf(ast.instructions(), 1);
470
471    let inst = ast.instruction(0);
472    assert.lengthOf(inst.operands(), 4);
473    assert.equal(inst.operand(0).value(), 1);
474    assert.equal(inst.operand(1).value(), 2);
475
476    assert.equal(inst.operand(2).name(),
477        "Volatile|Nontemporal|MakePointerVisible");
478    assert.equal(inst.operand(2).value(), 21);
479    assert.lengthOf(inst.operand(2).params(), 1);
480    assert.equal(inst.operand(2).params()[0].value(), 3);
481
482    assert.equal(inst.operand(3).name(),
483        "Aligned|MakePointerAvailable|NonPrivatePointer");
484    assert.equal(inst.operand(3).value(), 42);
485    assert.lengthOf(inst.operand(3).params(), 2);
486    assert.equal(inst.operand(3).params()[0].value(), 16);
487    assert.equal(inst.operand(3).params()[1].value(), 4);
488  });
489});
490