Skip to content

Commit 3cdaefa

Browse files
Update JS flags representation (#254)
* Update flag representation * Use `flags.repr().count()` instead of manually matching * Remove flag validation intrinsics * Validate that flags don't have extraneous bits set when lifting * grammar nit Co-authored-by: Alex Crichton <[email protected]>
1 parent f1682df commit 3cdaefa

File tree

3 files changed

+96
-118
lines changed

3 files changed

+96
-118
lines changed

crates/gen-host-js/src/lib.rs

Lines changed: 68 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,6 @@ enum Intrinsic {
5757
DataView,
5858
ValidateGuestChar,
5959
ValidateHostChar,
60-
ValidateFlags,
61-
ValidateFlags64,
6260
/// Implementation of https://tc39.es/ecma262/#sec-toint32.
6361
ToInt32,
6462
/// Implementation of https://tc39.es/ecma262/#sec-touint32.
@@ -97,8 +95,6 @@ impl Intrinsic {
9795
Intrinsic::DataView => "data_view",
9896
Intrinsic::ValidateGuestChar => "validate_guest_char",
9997
Intrinsic::ValidateHostChar => "validate_host_char",
100-
Intrinsic::ValidateFlags => "validate_flags",
101-
Intrinsic::ValidateFlags64 => "validate_flags64",
10298
Intrinsic::ToInt32 => "to_int32",
10399
Intrinsic::ToUint32 => "to_uint32",
104100
Intrinsic::ToInt16 => "to_int16",
@@ -417,23 +413,14 @@ impl Generator for Js {
417413
docs: &Docs,
418414
) {
419415
self.docs(docs);
420-
let repr = js_flags_repr(flags);
421-
let ty = repr.ty();
422-
let suffix = repr.suffix();
423416
self.src
424-
.ts(&format!("export type {} = {ty};\n", name.to_camel_case()));
425-
let name = name.to_shouty_snake_case();
426-
for (i, flag) in flags.flags.iter().enumerate() {
427-
let flag = flag.name.to_shouty_snake_case();
428-
self.src.js(&format!(
429-
"export const {name}_{flag} = {}{suffix};\n",
430-
1u128 << i,
431-
));
432-
self.src.ts(&format!(
433-
"export const {name}_{flag} = {}{suffix};\n",
434-
1u128 << i,
435-
));
417+
.ts(&format!("export interface {} {{\n", name.to_camel_case()));
418+
for flag in flags.flags.iter() {
419+
self.docs(&flag.docs);
420+
let name = flag.name.to_mixed_case();
421+
self.src.ts(&format!("{name}?: boolean,\n"));
436422
}
423+
self.src.ts("}\n");
437424
}
438425

439426
fn type_variant(
@@ -1523,56 +1510,80 @@ impl Bindgen for FunctionBindgen<'_> {
15231510
results.push(format!("[{}]", operands.join(", ")));
15241511
}
15251512

1513+
// This lowers flags from a dictionary of booleans in accordance with https://webidl.spec.whatwg.org/#es-dictionary.
15261514
Instruction::FlagsLower { flags, .. } => {
1527-
let repr = js_flags_repr(flags);
1528-
let validate = match repr {
1529-
JsFlagsRepr::Number => self.gen.intrinsic(Intrinsic::ValidateFlags),
1530-
JsFlagsRepr::Bigint => self.gen.intrinsic(Intrinsic::ValidateFlags64),
1531-
};
15321515
let op0 = &operands[0];
1533-
let len = flags.flags.len();
1534-
let n = repr.suffix();
1535-
let tmp = self.tmp();
1536-
let mask = (1u128 << len) - 1;
1516+
1517+
// Generate the result names.
1518+
for _ in 0..flags.repr().count() {
1519+
let tmp = self.tmp();
1520+
let name = format!("flags{tmp}");
1521+
// Default to 0 so that in the null/undefined case, everything is false by
1522+
// default.
1523+
self.src.js(&format!("let {name} = 0;\n"));
1524+
results.push(name);
1525+
}
1526+
15371527
self.src.js(&format!(
1538-
"const flags{tmp} = {validate}({op0}, {mask}{n});\n"
1528+
"if (typeof {op0} === \"object\" && {op0} !== null) {{\n"
15391529
));
1540-
match repr {
1541-
JsFlagsRepr::Number => {
1542-
results.push(format!("flags{}", tmp));
1543-
}
1544-
JsFlagsRepr::Bigint => {
1545-
for i in 0..flags.repr().count() {
1546-
let i = 32 * i;
1547-
results.push(format!("Number((flags{tmp} >> {i}n) & 0xffffffffn)",));
1530+
1531+
for (i, chunk) in flags.flags.chunks(32).enumerate() {
1532+
let result_name = &results[i];
1533+
1534+
self.src.js(&format!("{result_name} = "));
1535+
for (i, flag) in chunk.iter().enumerate() {
1536+
if i != 0 {
1537+
self.src.js(" | ");
15481538
}
1539+
1540+
let flag = flag.name.to_mixed_case();
1541+
self.src.js(&format!("Boolean({op0}.{flag}) << {i}"));
15491542
}
1543+
self.src.js(";\n");
15501544
}
1545+
1546+
self.src.js(&format!("\
1547+
}} else if ({op0} !== null && {op0} !== undefined) {{
1548+
throw new TypeError(\"only an object, undefined or null can be converted to flags\");
1549+
}}
1550+
"));
1551+
1552+
// We don't need to do anything else for the null/undefined
1553+
// case, since that's interpreted as everything false, and we
1554+
// already defaulted everyting to 0.
15511555
}
15521556

15531557
Instruction::FlagsLift { flags, .. } => {
1554-
let repr = js_flags_repr(flags);
1555-
let n = repr.suffix();
15561558
let tmp = self.tmp();
1557-
let operand = match repr {
1558-
JsFlagsRepr::Number => operands[0].clone(),
1559-
JsFlagsRepr::Bigint => {
1560-
self.src.js(&format!("let flags{tmp} = 0n;\n"));
1561-
for (i, op) in operands.iter().enumerate() {
1562-
let i = 32 * i;
1563-
self.src
1564-
.js(&format!("flags{tmp} |= BigInt({op}) << {i}n;\n",));
1565-
}
1566-
format!("flags{tmp}")
1559+
results.push(format!("flags{tmp}"));
1560+
1561+
if let Some(op) = operands.last() {
1562+
// We only need an extraneous bits check if the number of flags isn't a multiple
1563+
// of 32, because if it is then all the bits are used and there are no
1564+
// extraneous bits.
1565+
if flags.flags.len() % 32 != 0 {
1566+
let mask: u32 = 0xffffffff << (flags.flags.len() % 32);
1567+
self.src.js(&format!(
1568+
"\
1569+
if (({op} & {mask}) !== 0) {{
1570+
throw new TypeError('flags have extraneous bits set');
1571+
}}
1572+
"
1573+
));
15671574
}
1568-
};
1569-
let validate = match repr {
1570-
JsFlagsRepr::Number => self.gen.intrinsic(Intrinsic::ValidateFlags),
1571-
JsFlagsRepr::Bigint => self.gen.intrinsic(Intrinsic::ValidateFlags64),
1572-
};
1573-
let len = flags.flags.len();
1574-
let mask = (1u128 << len) - 1;
1575-
results.push(format!("{validate}({operand}, {mask}{n})"));
1575+
}
1576+
1577+
self.src.js(&format!("const flags{tmp} = {{\n"));
1578+
1579+
for (i, flag) in flags.flags.iter().enumerate() {
1580+
let flag = flag.name.to_mixed_case();
1581+
let op = &operands[i / 32];
1582+
let mask: u32 = 1 << (i % 32);
1583+
self.src.js(&format!("{flag}: Boolean({op} & {mask}),\n"));
1584+
}
1585+
1586+
self.src.js("};\n");
15761587
}
15771588

15781589
Instruction::VariantPayloadName => results.push("e".to_string()),
@@ -2309,26 +2320,6 @@ impl Js {
23092320
}
23102321
"),
23112322

2312-
Intrinsic::ValidateFlags => self.src.js("
2313-
export function validate_flags(flags, mask) {
2314-
if (!Number.isInteger(flags)) \
2315-
throw new TypeError('flags were not an integer');
2316-
if ((flags & ~mask) != 0)
2317-
throw new TypeError('flags have extraneous bits set');
2318-
return flags;
2319-
}
2320-
"),
2321-
2322-
Intrinsic::ValidateFlags64 => self.src.js("
2323-
export function validate_flags64(flags, mask) {
2324-
if (typeof flags !== 'bigint')
2325-
throw new TypeError('flags were not a bigint');
2326-
if ((flags & ~mask) != 0n)
2327-
throw new TypeError('flags have extraneous bits set');
2328-
return flags;
2329-
}
2330-
"),
2331-
23322323

23332324
Intrinsic::ToInt32 => self.src.js("
23342325
export function to_int32(val) {
@@ -2550,30 +2541,3 @@ impl Source {
25502541
self.ts.push_str(s);
25512542
}
25522543
}
2553-
2554-
enum JsFlagsRepr {
2555-
Number,
2556-
Bigint,
2557-
}
2558-
2559-
impl JsFlagsRepr {
2560-
fn ty(&self) -> &'static str {
2561-
match self {
2562-
JsFlagsRepr::Number => "number",
2563-
JsFlagsRepr::Bigint => "bigint",
2564-
}
2565-
}
2566-
fn suffix(&self) -> &'static str {
2567-
match self {
2568-
JsFlagsRepr::Number => "",
2569-
JsFlagsRepr::Bigint => "n",
2570-
}
2571-
}
2572-
}
2573-
2574-
fn js_flags_repr(f: &Flags) -> JsFlagsRepr {
2575-
match f.repr() {
2576-
FlagsRepr::U8 | FlagsRepr::U16 | FlagsRepr::U32(1) => JsFlagsRepr::Number,
2577-
FlagsRepr::U32(_) => JsFlagsRepr::Bigint,
2578-
}
2579-
}

tests/runtime/lists/host.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { addImportsToImports, Imports, FLAG32_B8, FLAG64_B9 } from "./imports.js";
1+
import { addImportsToImports, Imports } from "./imports.js";
22
import { Exports } from "./exports.js";
33
import * as exports from "./exports.js";
44
import { getWasm, addWasiToImports } from "./helpers.js";
@@ -42,8 +42,22 @@ async function run() {
4242
assert.deepStrictEqual(Array.from(u16), [1]);
4343
assert.deepStrictEqual(Array.from(u32), [2]);
4444
assert.deepStrictEqual(Array.from(u64), [3n]);
45-
assert.deepStrictEqual(flag32, [FLAG32_B8]);
46-
assert.deepStrictEqual(flag64, [FLAG64_B9]);
45+
assert.deepStrictEqual(flag32, [{
46+
b0: false, b1: false, b2: false, b3: false, b4: false, b5: false, b6: false, b7: false,
47+
b8: true, b9: false, b10: false, b11: false, b12: false, b13: false, b14: false, b15: false,
48+
b16: false, b17: false, b18: false, b19: false, b20: false, b21: false, b22: false, b23: false,
49+
b24: false, b25: false, b26: false, b27: false, b28: false, b29: false, b30: false, b31: false,
50+
}]);
51+
assert.deepStrictEqual(flag64, [{
52+
b0: false, b1: false, b2: false, b3: false, b4: false, b5: false, b6: false, b7: false,
53+
b8: false, b9: true, b10: false, b11: false, b12: false, b13: false, b14: false, b15: false,
54+
b16: false, b17: false, b18: false, b19: false, b20: false, b21: false, b22: false, b23: false,
55+
b24: false, b25: false, b26: false, b27: false, b28: false, b29: false, b30: false, b31: false,
56+
b32: false, b33: false, b34: false, b35: false, b36: false, b37: false, b38: false, b39: false,
57+
b40: false, b41: false, b42: false, b43: false, b44: false, b45: false, b46: false, b47: false,
58+
b48: false, b49: false, b50: false, b51: false, b52: false, b53: false, b54: false, b55: false,
59+
b56: false, b57: false, b58: false, b59: false, b60: false, b61: false, b62: false, b63: false,
60+
}]);
4761
},
4862
unalignedRoundtrip2(record, f32, f64, string, list) {
4963
assert.deepStrictEqual(Array.from(record), [{ a: 10, b: 11n }]);

tests/runtime/records/host.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,25 +29,25 @@ async function run() {
2929
wasm.testImports();
3030
assert.deepEqual(wasm.multipleResults(), [100, 200]);
3131
assert.deepStrictEqual(wasm.swapTuple([1, 2]), [2, 1]);
32-
assert.deepEqual(wasm.roundtripFlags1(exports.F1_A), exports.F1_A);
33-
assert.deepEqual(wasm.roundtripFlags1(0), 0);
34-
assert.deepEqual(wasm.roundtripFlags1(exports.F1_A | exports.F1_B), exports.F1_A | exports.F1_B);
32+
assert.deepEqual(wasm.roundtripFlags1({ a: true }), { a: true, b: false });
33+
assert.deepEqual(wasm.roundtripFlags1({}), { a: false, b: false });
34+
assert.deepEqual(wasm.roundtripFlags1({ a: true, b: true }), { a: true, b: true });
3535

36-
assert.deepEqual(wasm.roundtripFlags2(exports.F2_C), exports.F2_C);
37-
assert.deepEqual(wasm.roundtripFlags2(0), 0);
38-
assert.deepEqual(wasm.roundtripFlags2(exports.F2_D), exports.F2_D);
39-
assert.deepEqual(wasm.roundtripFlags2(exports.F2_C | exports.F2_E), exports.F2_C | exports.F2_E);
36+
assert.deepEqual(wasm.roundtripFlags2({ c: true }), { c: true, d: false, e: false });
37+
assert.deepEqual(wasm.roundtripFlags2({}), { c: false, d: false, e: false });
38+
assert.deepEqual(wasm.roundtripFlags2({ d: true }), { c: false, d: true, e: false });
39+
assert.deepEqual(wasm.roundtripFlags2({ c: true, e: true }), { c: true, d: false, e: true });
4040

4141
{
42-
const { a, b } = wasm.roundtripRecord1({ a: 8, b: 0 });
42+
const { a, b } = wasm.roundtripRecord1({ a: 8, b: {} });
4343
assert.deepEqual(a, 8);
44-
assert.deepEqual(b, 0);
44+
assert.deepEqual(b, { a: false, b: false });
4545
}
4646

4747
{
48-
const { a, b } = wasm.roundtripRecord1({ a: 0, b: exports.F1_A | exports.F1_B });
48+
const { a, b } = wasm.roundtripRecord1({ a: 0, b: { a: true, b: true } });
4949
assert.deepEqual(a, 0);
50-
assert.deepEqual(b, exports.F1_A | exports.F1_B);
50+
assert.deepEqual(b, { a: true, b: true });
5151
}
5252

5353
assert.deepStrictEqual(wasm.tuple0([]), []);

0 commit comments

Comments
 (0)