Update fidlviz for v2 wire format
This CL adds a checkbox that enables use of the v2 wire format. It is
checked by default.
Change-Id: I843af7e71d922ed7e3f0915eacca5d04212e0043
Reviewed-on: https://fuchsia-review.googlesource.com/c/fidl-misc/+/584462
Reviewed-by: Benjamin Prosnitz <bprosnitz@google.com>
diff --git a/fidlviz/index.html b/fidlviz/index.html
index 7d3784c..af2dd4a 100644
--- a/fidlviz/index.html
+++ b/fidlviz/index.html
@@ -45,9 +45,17 @@
<div id="OutputPart" class="split-item split-item--second">
<div class="part-header">
<h2 id="OutputTitle" class="part-title"> </h2>
- <input id="OutputSlider" class="slider"
- type="range" min="1" max="8"
- value="8" list="Tickmarks">
+ <div class="part-form">
+ <div id="OutputV2Control">
+ <input id="OutputV2Checkbox" type="checkbox" checked>
+ <label for="OutputV2Checkbox">Use V2 wire format</label>
+ </div>
+ <div>
+ <input id="OutputSlider" class="slider"
+ type="range" min="1" max="8"
+ value="8" list="Tickmarks">
+ </div>
+ </div>
</div>
<div id="OutputEditor" class="editor"></div>
</div>
diff --git a/fidlviz/script.js b/fidlviz/script.js
index e8c47f6..330c5f3 100644
--- a/fidlviz/script.js
+++ b/fidlviz/script.js
@@ -632,6 +632,7 @@
case "vector":
case "enum":
case "envelope":
+ case "pad4":
case "box":
case "table":
case "union": {
@@ -653,10 +654,14 @@
push(node.type + " {");
newline();
for (const field of node.fields) {
- push(INDENT.repeat(indent + 1));
+ if (field.type !== "$out") {
+ push(INDENT.repeat(indent + 1));
+ }
thunks.push(helper(field, indent + 1, /* topLevelObject = */ false));
- push(",");
- newline();
+ if (field.type !== "$out") {
+ push(",");
+ newline();
+ }
}
push(INDENT.repeat(indent) + "}");
}
@@ -834,7 +839,7 @@
node.lowered.push(lowered);
// If there is already a loweredFrom, overwrite it. Usually this is when a
// lowering stage copies a whole node (including its loweredFrom) field, and
- // then calls addLowered. Ocassionally there genuinely are multiple things it
+ // then calls addLowered. Occasionally there genuinely are multiple things it
// was loweredFrom, like bits/enums -- the whole structure and the value
// inside all lower the same output. In these cases letting the last
// addLowered call win works -- it will be the largest structure, the whole
@@ -869,19 +874,19 @@
}
// Helper combinator for lowering.
-function doLowering(tree, self, helper) {
+function doLowering(tree, config, self, helper) {
function augmentedHelper(node) {
const result = helper(node);
if (result !== undefined) {
// Reapply self. A proper lowering should not get into infinite recursion.
- return self(result);
+ return self(result, config);
}
if ("fields" in node) {
- return {...copyNode(node), fields: node.fields.map(self)};
+ return {...copyNode(node), fields: node.fields.map(f => self(f, config))};
}
// Very important that this copies the node rather than returning the same
// object. Otherwise highlighting gets into infinite recursion.
- return {...copyNode(node)};
+ return copyNode(node);
}
const lowered = augmentedHelper(tree);
addLowered(tree, lowered);
@@ -889,8 +894,8 @@
}
// Lowering stage 1: tables and unions.
-function lower1(tree) {
- return doLowering(tree, lower1, node => {
+function lower1(tree, config) {
+ return doLowering(tree, config, lower1, node => {
switch (node.type) {
case "table": {
if (node.fields.length === 0) {
@@ -983,8 +988,8 @@
}
// Lowering stage 2: bits and enums. Assumes earlier lowerings are done.
-function lower2(tree) {
- return doLowering(tree, lower2, node => {
+function lower2(tree, config) {
+ return doLowering(tree, config, lower2, node => {
switch (node.type) {
case "bits": {
const type = (node.fields[0].signed === "u" ? "uint" : "int")
@@ -1022,8 +1027,8 @@
}
// Lowering stage 3: strings. Assumes earlier lowerings are done.
-function lower3(tree) {
- return doLowering(tree, lower3, node => {
+function lower3(tree, config) {
+ return doLowering(tree, config, lower3, node => {
switch (node.type) {
case "null": {
if (node.kind !== "string") {
@@ -1076,8 +1081,8 @@
}
// Lowering stage 4: vectors. Assumes earlier lowerings are done.
-function lower4(tree) {
- return doLowering(tree, lower4, node => {
+function lower4(tree, config) {
+ return doLowering(tree, config, lower4, node => {
switch (node.type) {
case "null": {
if (node.kind !== "vector") {
@@ -1147,7 +1152,7 @@
}
// Lowering stage 5: handles and envelopes. Assumes earlier lowerings are done.
-function lower5(tree) {
+function lower5(tree, config) {
function countPresentHandles(node) {
if (node.type === "handle") {
return 1;
@@ -1162,7 +1167,7 @@
return 0;
}
- return doLowering(tree, lower5, node => {
+ return doLowering(tree, config, lower5, node => {
switch (node.type) {
case "null": {
if (node.kind !== "handle") {
@@ -1184,6 +1189,13 @@
}
case "envelope": {
if (node.fields.length === 0) {
+ if (config.v2Enabled) {
+ return {
+ type: "$pointer",
+ value: "absent",
+ note: node.note,
+ };
+ }
return {
type: "struct",
fields: [
@@ -1198,9 +1210,45 @@
};
}
disableAddLowered = true;
- const bytes = countTotalBytes(lowerBytes(lower6(lower5(node.fields[0]))));
+ const payload = lowerBytes(lower6(lower5(node.fields[0], config), config), config);
+ const bytes = countTotalBytes(payload);
disableAddLowered = false;
const handles = countPresentHandles(node.fields[0]);
+ if (config.v2Enabled) {
+ const lastField = payload.fields[payload.fields.length - 1]
+ if (bytes === 8 && lastField.padding && lastField.size >= 4) {
+ return {
+ type: "struct",
+ fields: [
+ {
+ ...copyNode(node),
+ type: "pad4",
+ note: "Pad inlined envelope value to 4 bytes"
+ },
+ {type: "int", signed: "u", width: 16,
+ orig: handles.toString(), value: handles,
+ note: `Envelope: ${handles} handle${plural(handles)}`},
+ {type: "int", signed: "u", width: 16, orig: "1", value: 1,
+ note: "Envelope flags: inlined representation"},
+ ],
+ note: node.note,
+ };
+ }
+ return {
+ type: "struct",
+ fields: [
+ {type: "int", signed: "u", width: 32, orig: bytes.toString(), value: bytes,
+ note: `Envelope: ${bytes} byte${plural(bytes)}`},
+ {type: "int", signed: "u", width: 16, orig: handles.toString(), value: handles,
+ note: `Envelope: ${handles} handle${plural(handles)}`},
+ {type: "int", signed: "u", width: 16, orig: "0", value: 0,
+ note: "Envelope flags: out-of-line representation"},
+ {type: "$out", fields: node.fields,
+ note: "Envelope: out-of-line data"},
+ ],
+ note: node.note,
+ };
+ }
return {
type: "struct",
fields: [
@@ -1218,9 +1266,9 @@
});
}
-// Lowering stage 6: boxes. Assumes earlier lowerings are done.
-function lower6(tree) {
- return doLowering(tree, lower6, node => {
+// Lowering stage 6: arrays and boxes. Assumes earlier lowerings are done.
+function lower6(tree, config) {
+ return doLowering(tree, config, lower6, node => {
switch (node.type) {
case "null": {
if (node.kind !== "struct") {
@@ -1261,8 +1309,8 @@
}
// Final lowering stage, to bytes. Assumes all earlier lowerings are done.
-function lowerBytes(tree) {
- // object can be "primary", "secondary", or false.
+function lowerBytes(tree, config) {
+ // object can be "primary", "secondary", "pad4", or false.
function lower(tree, object) {
const lowered = helper(tree, object);
addLowered(tree, lowered);
@@ -1341,6 +1389,9 @@
fields: [lower(node.fields[0], "secondary")],
};
}
+ case "pad4": {
+ return lower(node.fields[0], "pad4");
+ }
case "struct": {
const fields = [];
let size = 0, align = 0;
@@ -1390,7 +1441,10 @@
}
padToAlign(align, pad => `${pad} byte${plural(pad)} tail padding for `
+ `struct's ${pad}-byte alignment`);
- if (object) {
+ if (object === "pad4") {
+ padToAlign(4, pad => `${pad} byte${plural(pad)} tail padding for `
+ + `object inlined in envelope`);
+ } else if (object) {
padToAlign(8, pad => `${pad} byte${plural(pad)} tail padding for `
+ `${object} object ${align}-byte alignment`);
}
@@ -1440,12 +1494,12 @@
// Applies lowering functions from...to inclusive.
// 7 means lowerBytes.
-function lowerFromTo(tree, from, to) {
+function lowerFromTo(tree, from, to, config) {
const lowerings = [
lower1, lower2, lower3, lower4, lower5, lower6, lowerBytes
];
for (let i = from - 1; i < to; i++) {
- tree = lowerings[i](tree);
+ tree = lowerings[i](tree, config);
}
return tree;
}
@@ -1660,6 +1714,8 @@
const $inputEditor = ace.edit("InputEditor", inputConfig);
const $outputEditor = ace.edit("OutputEditor", outputConfig);
const $outputSlider = document.getElementById("OutputSlider");
+ const $outputV2Control = document.getElementById("OutputV2Control");
+ const $outputV2Checkbox = document.getElementById("OutputV2Checkbox");
const $outputTitle = document.getElementById("OutputTitle");
const $outputTooltip = document.getElementById("OutputTooltip");
const $resetButton = document.getElementById("ResetButton");
@@ -1731,9 +1787,11 @@
if (debugEnabled) {
$debugButton.classList.add("button--active");
$outputSlider.max = LAST_DEBUG_OUTPUT_MODE;
+ $outputV2Control.hidden = true;
} else {
$debugButton.classList.remove("button--active");
$outputSlider.max = LAST_OUTPUT_MODE;
+ $outputV2Control.hidden = false;
}
// Skip this at very beginning when called from restore().
if (restoring === undefined) {
@@ -1895,6 +1953,10 @@
const outputMode = $outputSlider.value;
const previouslyHadError = state.error;
+ const config = {
+ v2Enabled: $outputV2Checkbox.checked,
+ };
+
function getOutput(input) {
$inputEditor.session.clearAnnotations();
state.error = false;
@@ -1921,7 +1983,7 @@
return stringify(tree);
}
}
- const outputTree = lowerFromTo(state.tree, 1, outputMode - 1);
+ const outputTree = lowerFromTo(state.tree, 1, outputMode - 1, config);
state.outputTree = outputTree;
return outputMode === LAST_OUTPUT_MODE ? formatBytes(outputTree) : format(outputTree);
} catch (ex) {
@@ -2365,6 +2427,7 @@
outputModeWhenDebugIs = {false: $outputSlider.max, true: $outputSlider.min};
}
setOuputMode(outputModeWhenDebugIs[debugEnabled]);
+ $outputV2Checkbox.checked = true;
// Hidden at first to avoid a flash from default 50% editor split to the
// restored value. This also applies for the output slider (Debug or not
// Debug can cause flash). So we just hide the whole container till ready.
@@ -2423,6 +2486,13 @@
setOuputMode($outputSlider.value);
update();
};
+ // Update the output view whenever the v2 checkox changes.
+ $outputV2Checkbox.oninput = () => {
+ if (restoreInProgress) {
+ return;
+ }
+ update();
+ };
// Also allow moving the slider using Ctrl-[ and Ctrl-].
document.addEventListener("keydown", (e) => {
let delta;
diff --git a/fidlviz/style.css b/fidlviz/style.css
index d3ffdbd..8a05d9e 100644
--- a/fidlviz/style.css
+++ b/fidlviz/style.css
@@ -96,6 +96,12 @@
text-overflow: ellipsis;
}
+.part-form {
+ display: flex;
+ gap: 20px;
+ align-items: center;
+}
+
.button--active {
font-weight: bold;
}