tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

br-on-cast.js (10283B)


      1 function typingModule(types, from, to, brParams, branchResults, fallthroughResults) {
      2  return `(module
      3    ${types}
      4    (func
      5      (param ${brParams.join(' ')})
      6      (result ${branchResults.join(' ')})
      7 
      8      (block (result ${fallthroughResults.join(' ')})
      9        (; push params onto the stack in the same order as they appear, leaving
     10          the last param at the top of the stack. ;)
     11        ${brParams.map((_, i) => `local.get ${i}`).join('\n')}
     12        br_on_cast 1 ${from} ${to}
     13      )
     14      unreachable
     15    )
     16  )`;
     17 }
     18 
     19 function validTyping(types, from, to, brParams, branchResults, fallthroughResults) {
     20  wasmValidateText(typingModule(types, from, to, brParams, branchResults, fallthroughResults));
     21 }
     22 
     23 function invalidTyping(types, from, to, brParams, branchResults, fallthroughResults, error) {
     24  wasmFailValidateText(typingModule(types, from, to, brParams, branchResults, fallthroughResults), error);
     25 }
     26 
     27 // valid: eqref -> struct
     28 validTyping('(type $a (struct))', 'eqref', '(ref $a)', ['eqref'], ['(ref $a)'], ['eqref']);
     29 // valid: eqref -> struct (and looser types on results)
     30 validTyping('(type $a (struct))', 'eqref', '(ref $a)', ['eqref'], ['(ref null $a)'], ['anyref']);
     31 // valid: eqref -> nullable struct (note that fallthrough becomes non-nullable)
     32 validTyping('(type $a (struct))', 'eqref', '(ref null $a)', ['eqref'], ['(ref null $a)'], ['(ref eq)']);
     33 // valid: struct -> struct (from anyref)
     34 validTyping('(type $a (struct))', 'anyref', '(ref $a)', ['(ref $a)'], ['(ref $a)'], ['anyref']);
     35 // valid: struct -> struct (canonicalized)
     36 validTyping('(type $a (struct)) (type $b (struct))', '(ref $a)', '(ref $b)', ['(ref $a)'], ['(ref $b)'], ['(ref $b)']);
     37 // valid: nullable struct -> non-nullable struct (canonicalized)
     38 validTyping('(type $a (struct)) (type $b (struct))', '(ref null $a)', '(ref $b)', ['(ref null $a)'], ['(ref $b)'], ['(ref null $a)']);
     39 // valid: nullable struct -> nullable struct (canonicalized)
     40 validTyping('(type $a (struct)) (type $b (struct))', '(ref null $a)', '(ref null $b)', ['(ref null $a)'], ['(ref null $a)'], ['(ref $a)']);
     41 // valid: eqref -> struct with extra arg
     42 validTyping('(type $a (struct))', 'eqref', '(ref $a)', ['i32', 'eqref'], ['i32', '(ref $a)'], ['i32', 'eqref']);
     43 // valid: eqref -> struct with two extra args
     44 validTyping('(type $a (struct))', 'eqref', '(ref $a)', ['i32', 'f32', 'eqref'], ['i32', 'f32', '(ref $a)'], ['i32', 'f32', 'eqref']);
     45 
     46 // invalid: block result type must have slot for casted-to type
     47 invalidTyping('(type $a (struct))', 'eqref', '(ref $a)', ['eqref'], [], ['eqref'], /type mismatch/);
     48 // invalid: block result type must be supertype of casted-to type
     49 invalidTyping('(type $a (struct)) (type $b (struct (field i32)))', 'eqref', '(ref $a)', ['eqref'], ['(ref $b)'], ['(ref $a)'], /type mismatch/);
     50 // invalid: input is missing extra i32 from the branch target type
     51 invalidTyping('(type $a (struct))', 'eqref', '(ref $a)', ['f32', 'eqref'], ['i32', 'f32', '(ref $a)'], ['i32', 'f32', 'eqref'], /popping value/);
     52 // invalid: input has extra [i32, f32] swapped from the branch target type
     53 invalidTyping('(type $a (struct))', 'eqref', '(ref $a)', ['i32', 'f32', 'eqref'], ['f32', 'i32', '(ref $a)'], ['i32', 'f32', 'eqref'], /type mismatch/);
     54 // invalid: input has extra [i32, f32] swapped from the branch fallthrough type
     55 invalidTyping('(type $a (struct))', 'eqref', '(ref $a)', ['i32', 'f32', 'eqref'], ['i32', 'f32', '(ref $a)'], ['f32', 'i32', 'eqref'], /type mismatch/);
     56 // invalid: casting to non-nullable but fallthrough not nullable
     57 invalidTyping('(type $a (struct))', 'eqref', '(ref $a)', ['eqref'], ['(ref $a)'], ['(ref eq)'], /type mismatch/);
     58 // invalid: struct -> struct (same recursion group)
     59 invalidTyping('(rec (type $a (struct)) (type $b (struct)))', '(ref $a)', '(ref $b)', ['(ref $a)'], ['(ref $b)'], ['(ref $a)'], /type mismatch/);
     60 
     61 // Simple runtime test of casting
     62 {
     63  let { makeA, makeB, isA, isB } = wasmEvalText(`(module
     64    (type $a (sub (struct)))
     65    (type $b (sub $a (struct (field i32))))
     66 
     67    (func (export "makeA") (result eqref)
     68      struct.new_default $a
     69    )
     70 
     71    (func (export "makeB") (result eqref)
     72      struct.new_default $b
     73    )
     74 
     75    (func (export "isA") (param eqref) (result i32)
     76      (block (result (ref $a))
     77        local.get 0
     78        br_on_cast 0 anyref (ref $a)
     79 
     80        i32.const 0
     81        br 1
     82      )
     83      drop
     84      i32.const 1
     85    )
     86 
     87    (func (export "isB") (param eqref) (result i32)
     88      (block (result (ref $a))
     89        local.get 0
     90        br_on_cast 0 anyref (ref $b)
     91 
     92        i32.const 0
     93        br 1
     94      )
     95      drop
     96      i32.const 1
     97    )
     98  )`).exports;
     99 
    100  let a = makeA();
    101  let b = makeB();
    102 
    103  assertEq(isA(a), 1);
    104  assertEq(isA(b), 1);
    105  assertEq(isB(a), 0);
    106  assertEq(isB(b), 1);
    107 }
    108 
    109 // Runtime test of casting with extra values
    110 {
    111  function assertEqResults(a, b) {
    112    if (!(a instanceof Array)) {
    113      a = [a];
    114    }
    115    if (!(b instanceof Array)) {
    116      b = [b];
    117    }
    118    if (a.length !== b.length) {
    119      assertEq(a.length, b.length);
    120    }
    121    for (let i = 0; i < a.length; i++) {
    122      let x = a[i];
    123      let y = b[i];
    124      // intentionally use loose equality to allow bigint to compare equally
    125      // to number, as can happen with how we use the JS-API here.
    126      assertEq(x == y, true, `expected ${x} == ${y}, at ${i}`);
    127    }
    128  }
    129 
    130  function testExtra(values) {
    131    let { makeT, makeF, select } = wasmEvalText(`(module
    132      (type $t (struct))
    133      (type $f (struct (field i32)))
    134 
    135      (func (export "makeT") (result eqref)
    136        struct.new_default $t
    137      )
    138      (func (export "makeF") (result eqref)
    139        struct.new_default $f
    140      )
    141 
    142      (func (export "select") (param eqref) (result ${values.map((type) => type).join(" ")})
    143        (block (result (ref $t))
    144          local.get 0
    145          br_on_cast 0 anyref (ref $t)
    146 
    147          ${values.map((type, i) => `${type}.const ${values.length + i}`).join("\n")}
    148          br 1
    149        )
    150        drop
    151        ${values.map((type, i) => `${type}.const ${i}`).join("\n")}
    152      )
    153    )`).exports;
    154 
    155    let t = makeT();
    156    let f = makeF();
    157 
    158    let trueValues = values.map((type, i) => i);
    159    let falseValues = values.map((type, i) => values.length + i);
    160 
    161    assertEqResults(select(t), trueValues);
    162    assertEqResults(select(f), falseValues);
    163  }
    164 
    165  // multiples of primitive valtypes
    166  for (let valtype of ['i32', 'i64', 'f32', 'f64']) {
    167    testExtra([valtype]);
    168    testExtra([valtype, valtype]);
    169    testExtra([valtype, valtype, valtype]);
    170    testExtra([valtype, valtype, valtype, valtype, valtype, valtype, valtype, valtype]);
    171  }
    172 
    173  // random sundry of valtypes
    174  testExtra(['i32', 'f32', 'i64', 'f64']);
    175  testExtra(['i32', 'f32', 'i64', 'f64', 'i32', 'f32', 'i64', 'f64']);
    176 }
    177 
    178 // Runtime test of casting with extra values passed through the branch
    179 {
    180  function assertEqResults(a, b) {
    181    if (!(a instanceof Array)) {
    182      a = [a];
    183    }
    184    if (!(b instanceof Array)) {
    185      b = [b];
    186    }
    187    if (a.length !== b.length) {
    188      assertEq(a.length, b.length);
    189    }
    190    for (let i = 0; i < a.length; i++) {
    191      let x = a[i];
    192      let y = b[i];
    193      // intentionally use loose equality to allow bigint to compare equally
    194      // to number, as can happen with how we use the JS-API here.
    195      assertEq(x == y, true, `expected ${x} == ${y}, at ${i}`);
    196    }
    197  }
    198 
    199  function testExtra(values) {
    200    let types = values.map((type) => type).join(" ");
    201 
    202    // Construct a `select` function which takes `values` twice and a value to
    203    // cast. It will return the first values if the 'true' value is passed,
    204    // otherwise it will return the 'false' values.
    205    let { makeT, makeF, select } = wasmEvalText(`(module
    206      (type $t (struct))
    207      (type $f (struct (field i32)))
    208 
    209      (func (export "makeT") (result eqref)
    210        struct.new_default $t
    211      )
    212      (func (export "makeF") (result eqref)
    213        struct.new_default $f
    214      )
    215 
    216      (func (export "select") (param ${types} ${types} eqref) (result ${types})
    217        (block (result ${types} anyref)
    218          (block (result ${types} (ref $t))
    219            ;; branch to 0, passing along 'true' values and the 'casted' value
    220            ${values.map((_, i) => `local.get ${i}`).join(" ")}
    221            local.get ${values.length * 2}
    222            br_on_cast 0 anyref (ref $t)
    223 
    224            ;; branch to 1, passing along 'false' values and the 'uncasted' value
    225            ${values.map((_, i) => `local.get ${values.length + i}`).join(" ")}
    226            local.get ${values.length * 2}
    227            br_on_cast_fail 1 anyref (ref $t)
    228 
    229            ;; cast and cast_fail of the same type cannot both fallthrough
    230            unreachable
    231          )
    232 
    233          ;; fallthrough, passing our values along
    234        )
    235        
    236        ;; drop the casted/uncasted value at the top, we don't need it
    237        drop
    238      )
    239    )`).exports;
    240 
    241    let t = makeT();
    242    let f = makeF();
    243 
    244    let trueValues = values.map((type, i) => type == 'i64' ? BigInt(i) : i);
    245    let falseValues = values.map((type, i) => {
    246      let value = values.length + i;
    247      return type == 'i64' ? BigInt(value) : value;
    248    });
    249 
    250    assertEqResults(select(...trueValues, ...falseValues, t), trueValues);
    251    assertEqResults(select(...trueValues, ...falseValues, f), falseValues);
    252  }
    253 
    254  // multiples of primitive valtypes
    255  for (let valtype of ['i32', 'i64', 'f32', 'f64']) {
    256    testExtra([valtype]);
    257    testExtra([valtype, valtype]);
    258    testExtra([valtype, valtype, valtype]);
    259    testExtra([valtype, valtype, valtype, valtype, valtype, valtype, valtype, valtype]);
    260  }
    261 
    262  // random sundry of valtypes
    263  testExtra(['i32', 'f32', 'i64', 'f64']);
    264  testExtra(['i32', 'f32', 'i64', 'f64', 'i32', 'f32', 'i64', 'f64']);
    265 }
    266 
    267 // This test causes the `values` vector returned by
    268 // `OpIter<Policy>::readBrOnCast` to contain three entries, the last of which
    269 // is the argument, hence is reftyped.  This is used to verify an assertion to
    270 // that effect in FunctionCompiler::brOnCastCommon.
    271 {
    272    let tOnCast =
    273    `(module
    274       (type $a (struct))
    275       (func (export "onCast") (param f32 i32 eqref) (result f32 i32 (ref $a))
    276         local.get 0
    277         local.get 1
    278         local.get 2
    279         br_on_cast 0 anyref (ref $a)
    280         unreachable
    281       )
    282     )`;
    283    let { onCast } = wasmEvalText(tOnCast).exports;
    284 }