globals.js (19111B)
1 const { Instance, Module, LinkError } = WebAssembly; 2 3 // Locally-defined globals 4 assertErrorMessage(() => wasmEvalText(`(module (global))`), SyntaxError, /wasm text error/); 5 // A global field in the text format is valid with an empty expression, but this produces an invalid module 6 assertErrorMessage(() => wasmEvalText(`(module (global i32))`), WebAssembly.CompileError, /popping value/); 7 assertErrorMessage(() => wasmEvalText(`(module (global (mut i32)))`), WebAssembly.CompileError, /popping value/); 8 9 // Initializer expressions. 10 wasmFailValidateText(`(module (global i32 (f32.const 13.37)))`, /type mismatch/); 11 wasmFailValidateText(`(module (global f64 (f32.const 13.37)))`, /type mismatch/); 12 13 wasmFailValidateText(`(module (global i32 (global.get 0)))`, /out of range/); 14 wasmFailValidateText(`(module (global i32 (global.get 1)) (global i32 (i32.const 1)))`, /out of range/); 15 16 // Test a well-defined global section. 17 function testInner(type, initialValue, nextValue, coercion) 18 { 19 var module = wasmEvalText(`(module 20 (global (mut ${type}) (${type}.const ${initialValue})) 21 (global ${type} (${type}.const ${initialValue})) 22 23 (func $get (result ${type}) (global.get 0)) 24 (func $set (param ${type}) (global.set 0 (local.get 0))) 25 26 (func $get_cst (result ${type}) (global.get 1)) 27 28 (export "get" (func $get)) 29 (export "get_cst" (func $get_cst)) 30 31 (export "set" (func $set)) 32 )`).exports; 33 34 assertEq(module.get(), coercion(initialValue)); 35 assertEq(module.set(coercion(nextValue)), undefined); 36 assertEq(module.get(), coercion(nextValue)); 37 38 assertEq(module.get_cst(), coercion(initialValue)); 39 } 40 41 testInner('i32', 13, 37, x => x|0); 42 testInner('f32', 13.37, 0.1989, Math.fround); 43 testInner('f64', 13.37, 0.1989, x => +x); 44 45 // Basic global shenanigans 46 { 47 const module = wasmEvalText(`(module 48 ;; -2 * (5 - (-10 + 20)) = 10 49 (global i32 (i32.mul (i32.const -2) (i32.sub (i32.const 5) (i32.add (i32.const -10) (i32.const 20))))) 50 ;; ((1 + 2) - (3 * 4)) = -9 51 (global i64 (i64.sub (i64.add (i64.const 1) (i64.const 2)) (i64.mul (i64.const 3) (i64.const 4)))) 52 53 (func (export "get0") (result i32) global.get 0) 54 (func (export "get1") (result i64) global.get 1) 55 )`).exports; 56 57 assertEq(module.get0(), 10); 58 assertEq(module.get1(), -9n); 59 } 60 61 // Example use of dynamic linking 62 { 63 // Make a memory for two dynamically-linked modules to share. Each module gets five pages. 64 const mem = new WebAssembly.Memory({ initial: 15, maximum: 15 }); 65 66 const mod1 = new WebAssembly.Module(wasmTextToBinary(`(module 67 (memory (import "env" "memory") 15 15) 68 (global $memBase (import "env" "__memory_base") i32) 69 (data (offset (global.get $memBase)) "Hello from module 1.") 70 (data (offset (i32.add (global.get $memBase) (i32.const 65536))) "Goodbye from module 1.") 71 )`)); 72 const instance1 = new WebAssembly.Instance(mod1, { 73 env: { 74 memory: mem, 75 __memory_base: 65536 * 5, // this module's memory starts at page 5 76 }, 77 }); 78 79 const mod2 = new WebAssembly.Module(wasmTextToBinary(`(module 80 (memory (import "env" "memory") 15 15) 81 (global $memBase (import "env" "__memory_base") i32) 82 (data (offset (global.get $memBase)) "Hello from module 2.") 83 (data (offset (i32.add (global.get $memBase) (i32.const 65536))) "Goodbye from module 2.") 84 )`)); 85 const instance2 = new WebAssembly.Instance(mod2, { 86 env: { 87 memory: mem, 88 __memory_base: 65536 * 10, // this module's memory starts at page 10 89 }, 90 }); 91 92 // All four strings should now be present in the memory. 93 94 function assertStringInMem(mem, str, addr) { 95 const bytes = new Uint8Array(mem.buffer).slice(addr, addr + str.length); 96 let memStr = String.fromCharCode(...bytes); 97 assertEq(memStr, str); 98 } 99 100 assertStringInMem(mem, "Hello from module 1.", 65536 * 5); 101 assertStringInMem(mem, "Goodbye from module 1.", 65536 * 6); 102 assertStringInMem(mem, "Hello from module 2.", 65536 * 10); 103 assertStringInMem(mem, "Goodbye from module 2.", 65536 * 11); 104 } 105 106 // Semantic errors. 107 wasmFailValidateText(`(module (global (mut i32) (i32.const 1337)) (func (global.set 1 (i32.const 0))))`, /(out of range)|(global index out of bounds)/); 108 wasmFailValidateText(`(module (global i32 (i32.const 1337)) (func (global.set 0 (i32.const 0))))`, /(can't write an immutable global)|(global is immutable)/); 109 110 // Big module with many variables: test that setting one doesn't overwrite the 111 // other ones. 112 function get_set(i, type) { 113 return ` 114 (func $get_${i} (result ${type}) (global.get ${i})) 115 (func $set_${i} (param ${type}) (global.set ${i} (local.get 0))) 116 `; 117 } 118 119 var module = wasmEvalText(`(module 120 (global (mut i32) (i32.const 42)) 121 (global (mut i32) (i32.const 10)) 122 (global (mut f32) (f32.const 13.37)) 123 (global (mut f64) (f64.const 13.37)) 124 (global (mut i32) (i32.const -18)) 125 126 ${get_set(0, 'i32')} 127 ${get_set(1, 'i32')} 128 ${get_set(2, 'f32')} 129 ${get_set(3, 'f64')} 130 ${get_set(4, 'i32')} 131 132 (export "get0" (func $get_0)) (export "set0" (func $set_0)) 133 (export "get1" (func $get_1)) (export "set1" (func $set_1)) 134 (export "get2" (func $get_2)) (export "set2" (func $set_2)) 135 (export "get3" (func $get_3)) (export "set3" (func $set_3)) 136 (export "get4" (func $get_4)) (export "set4" (func $set_4)) 137 )`).exports; 138 139 let values = [42, 10, Math.fround(13.37), 13.37, -18]; 140 let nextValues = [13, 37, Math.fround(-17.89), 9.3, -13]; 141 for (let i = 0; i < 5; i++) { 142 assertEq(module[`get${i}`](), values[i]); 143 assertEq(module[`set${i}`](nextValues[i]), undefined); 144 assertEq(module[`get${i}`](), nextValues[i]); 145 for (let j = 0; j < 5; j++) { 146 if (i === j) 147 continue; 148 assertEq(module[`get${j}`](), values[j]); 149 } 150 assertEq(module[`set${i}`](values[i]), undefined); 151 assertEq(module[`get${i}`](), values[i]); 152 } 153 154 // Initializer expressions can also be used in elem section initializers. 155 wasmFailValidateText(`(module (import "globals" "a" (global f32)) (table 4 funcref) (elem (global.get 0) $f) (func $f))`, /type mismatch/); 156 157 module = wasmEvalText(`(module 158 (import "globals" "a" (global i32)) 159 (table (export "tbl") 4 funcref) 160 (elem (global.get 0) $f) 161 (func $f) 162 (export "f" (func $f)) 163 )`, { 164 globals: { 165 a: 1 166 } 167 }).exports; 168 assertEq(module.f, module.tbl.get(1)); 169 170 // Import/export semantics. 171 module = wasmEvalText(`(module 172 (import "globals" "x" (global $g i32)) 173 (func $get (result i32) (global.get $g)) 174 (export "getter" (func $get)) 175 (export "value" (global 0)) 176 )`, { globals: {x: 42} }).exports; 177 178 assertEq(module.getter(), 42); 179 180 // assertEq() will not trigger @@toPrimitive, so we must have a cast here. 181 assertEq(Number(module.value), 42); 182 183 // Can only import numbers (no implicit coercions). 184 module = new Module(wasmTextToBinary(`(module 185 (global (import "globs" "i32") i32) 186 (global (import "globs" "f32") f32) 187 (global (import "globs" "f64") f32) 188 )`)); 189 190 const assertLinkFails = (m, imp, err) => { 191 assertErrorMessage(() => new Instance(m, imp), LinkError, err); 192 } 193 194 var imp = { 195 globs: { 196 i32: 0, 197 f32: Infinity, 198 f64: NaN 199 } 200 }; 201 202 let i = new Instance(module, imp); 203 204 for (let v of [ 205 null, 206 {}, 207 "42", 208 /not a number/, 209 false, 210 undefined, 211 Symbol(), 212 { valueOf() { return 42; } } 213 ]) { 214 imp.globs.i32 = v; 215 assertLinkFails(module, imp, /not a Number/); 216 217 imp.globs.i32 = 0; 218 imp.globs.f32 = v; 219 assertLinkFails(module, imp, /not a Number/); 220 221 imp.globs.f32 = Math.fround(13.37); 222 imp.globs.f64 = v; 223 assertLinkFails(module, imp, /not a Number/); 224 225 imp.globs.f64 = 13.37; 226 } 227 228 // Imported globals and locally defined globals use the same index space. 229 module = wasmEvalText(`(module 230 (import "globals" "x" (global i32)) 231 (global i32 (i32.const 1337)) 232 (export "imported" (global 0)) 233 (export "defined" (global 1)) 234 )`, { globals: {x: 42} }).exports; 235 236 assertEq(Number(module.imported), 42); 237 assertEq(Number(module.defined), 1337); 238 239 wasmFailValidateText(`(module (import "globals" "a" (global f32)) (global i32 (global.get 0)))`, /type mismatch/); 240 241 function testInitExpr(type, initialValue, nextValue, coercion, assertFunc = assertEq) { 242 var module = wasmEvalText(`(module 243 (import "globals" "a" (global ${type})) 244 245 (global $glob_mut (mut ${type}) (global.get 0)) 246 (global $glob_imm ${type} (global.get 0)) 247 248 (func $get0 (result ${type}) (global.get 0)) 249 250 (func $get1 (result ${type}) (global.get 1)) 251 (func $set1 (param ${type}) (global.set 1 (local.get 0))) 252 253 (func $get_cst (result ${type}) (global.get 2)) 254 255 (export "get0" (func $get0)) 256 (export "get1" (func $get1)) 257 (export "get_cst" (func $get_cst)) 258 259 (export "set1" (func $set1)) 260 (export "global_imm" (global $glob_imm)) 261 )`, { 262 globals: { 263 a: coercion(initialValue) 264 } 265 }).exports; 266 267 assertFunc(module.get0(), coercion(initialValue)); 268 assertFunc(module.get1(), coercion(initialValue)); 269 assertFunc(Number(module.global_imm), coercion(initialValue)); 270 271 assertEq(module.set1(coercion(nextValue)), undefined); 272 assertFunc(module.get1(), coercion(nextValue)); 273 assertFunc(module.get0(), coercion(initialValue)); 274 assertFunc(Number(module.global_imm), coercion(initialValue)); 275 276 assertFunc(module.get_cst(), coercion(initialValue)); 277 } 278 279 testInitExpr('i32', 13, 37, x => x|0); 280 testInitExpr('f32', 13.37, 0.1989, Math.fround); 281 testInitExpr('f64', 13.37, 0.1989, x => +x); 282 283 // Int64. 284 285 // Import and export 286 287 // Test inner 288 var initialValue = '0x123456789abcdef0'; 289 var nextValue = '0x531642753864975F'; 290 wasmAssert(`(module 291 (global (mut i64) (i64.const ${initialValue})) 292 (global i64 (i64.const ${initialValue})) 293 (func $get (result i64) (global.get 0)) 294 (func $set (param i64) (global.set 0 (local.get 0))) 295 (func $get_cst (result i64) (global.get 1)) 296 (export "get" (func $get)) 297 (export "get_cst" (func $get_cst)) 298 (export "set" (func $set)) 299 )`, [ 300 {type: 'i64', func: '$get', expected: initialValue}, 301 {type: 'i64', func: '$set', args: [`i64.const ${nextValue}`]}, 302 {type: 'i64', func: '$get', expected: nextValue}, 303 {type: 'i64', func: '$get_cst', expected: initialValue}, 304 ]); 305 306 // Custom NaN. 307 { 308 let dv = new DataView(new ArrayBuffer(8)); 309 module = wasmEvalText(`(module 310 (global $g f64 (f64.const -nan:0xe7ffff1591120)) 311 (global $h f32 (f32.const -nan:0x651234)) 312 (export "nan64" (global $g))(export "nan32" (global $h)) 313 )`, {}).exports; 314 315 dv.setFloat64(0, module.nan64, true); 316 assertEq(dv.getUint32(4, true), 0x7ff80000); 317 assertEq(dv.getUint32(0, true), 0x00000000); 318 319 dv.setFloat32(0, module.nan32, true); 320 assertEq(dv.getUint32(0, true), 0x7fc00000); 321 } 322 323 // WebAssembly.Global 324 { 325 const Global = WebAssembly.Global; 326 327 // These types should work: 328 assertEq(new Global({value: "i32"}) instanceof Global, true); 329 assertEq(new Global({value: "f32"}) instanceof Global, true); 330 assertEq(new Global({value: "f64"}) instanceof Global, true); 331 assertEq(new Global({value: "i64"}) instanceof Global, true); // No initial value works 332 333 // Coercion of init value; ".value" accessor 334 assertEq((new Global({value: "i32"}, 3.14)).value, 3); 335 assertEq((new Global({value: "f32"}, { valueOf: () => 33.5 })).value, 33.5); 336 assertEq((new Global({value: "f64"}, "3.25")).value, 3.25); 337 338 // Nothing special about NaN, it coerces just fine 339 assertEq((new Global({value: "i32"}, NaN)).value, 0); 340 341 // The default init value is zero. 342 assertEq((new Global({value: "i32"})).value, 0); 343 assertEq((new Global({value: "f32"})).value, 0); 344 assertEq((new Global({value: "f64"})).value, 0); 345 let mod = wasmEvalText(`(module 346 (import "" "g" (global i64)) 347 (func (export "f") (result i32) 348 (i64.eqz (global.get 0))))`, 349 {"":{g: new Global({value: "i64"})}}); 350 assertEq(mod.exports.f(), 1); 351 352 { 353 // "value" is enumerable 354 let x = new Global({value: "i32"}); 355 let s = ""; 356 for ( let i in x ) 357 s = s + i + ","; 358 if (getBuildConfiguration("release_or_beta")) { 359 assertEq(s, "valueOf,value,"); 360 } else { 361 assertEq(s, "type,valueOf,value,"); 362 } 363 } 364 365 // "value" is defined on the prototype, not on the object 366 assertEq("value" in Global.prototype, true); 367 368 // Can't set the value of an immutable global 369 assertErrorMessage(() => (new Global({value: "i32"})).value = 10, 370 TypeError, 371 /can't set value of immutable global/); 372 373 { 374 // Can set the value of a mutable global 375 let g = new Global({value: "i32", mutable: true}, 37); 376 g.value = 10; 377 assertEq(g.value, 10); 378 } 379 380 { 381 // Misc internal conversions 382 let g = new Global({value: "i32"}, 42); 383 384 // valueOf 385 assertEq(g - 5, 37); 386 387 // @@toStringTag 388 assertEq(g.toString(), "[object WebAssembly.Global]"); 389 } 390 391 { 392 // An exported global should appear as a Global instance: 393 let i = wasmEvalText(`(module (global (export "g") i32 (i32.const 42)))`); 394 395 assertEq(typeof i.exports.g, "object"); 396 assertEq(i.exports.g instanceof Global, true); 397 398 // An exported global can be imported into another instance even if 399 // it is an object: 400 let j = wasmEvalText(`(module 401 (global (import "" "g") i32) 402 (func (export "f") (result i32) 403 (global.get 0)))`, 404 { "": { "g": i.exports.g }}); 405 406 // And when it is then accessed it has the right value: 407 assertEq(j.exports.f(), 42); 408 } 409 410 // Identity of Global objects (independent of mutablity). 411 { 412 // When a global is exported twice, the two objects are the same. 413 let i = wasmEvalText(`(module 414 (global i32 (i32.const 0)) 415 (export "a" (global 0)) 416 (export "b" (global 0)))`); 417 assertEq(i.exports.a, i.exports.b); 418 419 // When a global is imported and then exported, the exported object is 420 // the same as the imported object. 421 let j = wasmEvalText(`(module 422 (import "" "a" (global i32)) 423 (export "x" (global 0)))`, 424 { "": {a: i.exports.a}}); 425 426 assertEq(i.exports.a, j.exports.x); 427 428 // When a global is imported twice (ie aliased) and then exported twice, 429 // the exported objects are the same, and are also the same as the 430 // imported object. 431 let k = wasmEvalText(`(module 432 (import "" "a" (global i32)) 433 (import "" "b" (global i32)) 434 (export "x" (global 0)) 435 (export "y" (global 1)))`, 436 { "": {a: i.exports.a, 437 b: i.exports.a}}); 438 439 assertEq(i.exports.a, k.exports.x); 440 assertEq(k.exports.x, k.exports.y); 441 } 442 443 // Mutability 444 { 445 let i = wasmEvalText(`(module 446 (global (export "g") (mut i32) (i32.const 37)) 447 (func (export "getter") (result i32) 448 (global.get 0)) 449 (func (export "setter") (param i32) 450 (global.set 0 (local.get 0))))`); 451 452 let j = wasmEvalText(`(module 453 (import "" "g" (global (mut i32))) 454 (func (export "getter") (result i32) 455 (global.get 0)) 456 (func (export "setter") (param i32) 457 (global.set 0 (local.get 0))))`, 458 {"": {g: i.exports.g}}); 459 460 // Initial values 461 assertEq(i.exports.g.value, 37); 462 assertEq(i.exports.getter(), 37); 463 assertEq(j.exports.getter(), 37); 464 465 // Set in i, observe everywhere 466 i.exports.setter(42); 467 468 assertEq(i.exports.g.value, 42); 469 assertEq(i.exports.getter(), 42); 470 assertEq(j.exports.getter(), 42); 471 472 // Set in j, observe everywhere 473 j.exports.setter(78); 474 475 assertEq(i.exports.g.value, 78); 476 assertEq(i.exports.getter(), 78); 477 assertEq(j.exports.getter(), 78); 478 479 // Set on global object, observe everywhere 480 i.exports.g.value = 197; 481 482 assertEq(i.exports.g.value, 197); 483 assertEq(i.exports.getter(), 197); 484 assertEq(j.exports.getter(), 197); 485 } 486 487 // Mutability of import declaration and imported value have to match 488 { 489 const mutErr = /imported global mutability mismatch/; 490 491 let m1 = new Module(wasmTextToBinary(`(module 492 (import "m" "g" (global i32)))`)); 493 494 // Mutable Global matched to immutable import 495 let gm = new Global({value: "i32", mutable: true}, 42); 496 assertErrorMessage(() => new Instance(m1, {m: {g: gm}}), 497 LinkError, 498 mutErr); 499 500 let m2 = new Module(wasmTextToBinary(`(module 501 (import "m" "g" (global (mut i32))))`)); 502 503 // Immutable Global matched to mutable import 504 let gi = new Global({value: "i32", mutable: false}, 42); 505 assertErrorMessage(() => new Instance(m2, {m: {g: gi}}), 506 LinkError, 507 mutErr); 508 509 // Constant value is the same as immutable Global 510 assertErrorMessage(() => new Instance(m2, {m: {g: 42}}), 511 LinkError, 512 mutErr); 513 } 514 515 // TEST THIS LAST 516 517 // "value" is deletable 518 assertEq(delete Global.prototype.value, true); 519 assertEq("value" in Global.prototype, false); 520 521 // ADD NO MORE TESTS HERE! 522 } 523 524 // Standard wat syntax: the parens around the initializer expression are 525 // optional. 526 { 527 let i1 = wasmEvalText( 528 `(module 529 (global $g i32 i32.const 37) 530 (func (export "f") (result i32) (global.get $g)))`); 531 assertEq(i1.exports.f(), 37); 532 533 let i2 = wasmEvalText( 534 `(module 535 (global $g (mut f64) f64.const 42.0) 536 (func (export "f") (result f64) (global.get $g)))`); 537 assertEq(i2.exports.f(), 42); 538 539 let i3 = wasmEvalText( 540 `(module 541 (global $x (import "m" "x") i32) 542 (global $g i32 global.get $x) 543 (func (export "f") (result i32) (global.get $g)))`, 544 {m:{x:86}}); 545 assertEq(i3.exports.f(), 86); 546 }