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