Skip to content

Commit 95c579f

Browse files
feat(jco): add flag to enable tracing details (#150)
* feat(js-component-bindgen): add tracing flag * feat(jco): add tracing flag * chore: add test coverage for tracing flag * chore: disable array buffer in stringify * Update crates/js-component-bindgen/src/intrinsics.rs --------- Co-authored-by: Eduardo Rodrigues <[email protected]> Co-authored-by: Guy Bedford <[email protected]>
1 parent 3a11acf commit 95c579f

File tree

10 files changed

+117
-7
lines changed

10 files changed

+117
-7
lines changed

bin/self_build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ fn main() -> Result<()> {
5151
base64_cutoff: 5000_usize,
5252
tla_compat: true,
5353
valid_lifting_optimization: false,
54+
tracing: false,
5455
};
5556

5657
let transpiled = js_component_bindgen::transpile(&adapted_component, opts)?;

build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ fn main() -> Result<()> {
3535
tla_compat: false,
3636
valid_lifting_optimization: false,
3737
base64_cutoff: 0,
38+
tracing: false,
3839
};
3940

4041
let files = generate_types(name, resolve, world, opts)?;

crates/js-component-bindgen-component/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ impl Guest for JsComponentBindgenComponent {
5959
.tla_compat
6060
.unwrap_or(options.compat.unwrap_or(false)),
6161
valid_lifting_optimization: options.valid_lifting_optimization.unwrap_or(false),
62+
tracing: options.tracing.unwrap_or(false),
6263
};
6364

6465
let js_component_bindgen::Transpiled {
@@ -129,6 +130,7 @@ impl Guest for JsComponentBindgenComponent {
129130
tla_compat: opts.tla_compat.unwrap_or(false),
130131
valid_lifting_optimization: false,
131132
base64_cutoff: 0,
133+
tracing: false,
132134
};
133135

134136
let files = generate_types(name, resolve, world, opts).map_err(|e| e.to_string())?;

crates/js-component-bindgen-component/wit/js-component-bindgen.wit

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ world js-component-bindgen {
3636
/// Disable verification of component Wasm data structures when
3737
/// lifting as a production optimization
3838
valid-lifting-optimization: option<bool>,
39+
40+
/// Whether or not to emit `tracing` calls on function entry/exit.
41+
tracing: option<bool>,
3942
}
4043

4144
variant wit {

crates/js-component-bindgen/src/function_bindgen.rs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ pub struct FunctionBindgen<'a> {
6262
pub memory: Option<&'a String>,
6363
pub realloc: Option<&'a String>,
6464
pub post_return: Option<&'a String>,
65+
pub tracing_prefix: Option<&'a String>,
6566
pub encoding: StringEncoding,
6667
pub callee: &'a str,
6768
}
@@ -1001,11 +1002,26 @@ impl Bindgen for FunctionBindgen<'_> {
10011002
Instruction::IterBasePointer => results.push("base".to_string()),
10021003

10031004
Instruction::CallWasm { sig, .. } => {
1004-
self.bind_results(sig.results.len(), results);
1005+
let sig_results_length = sig.results.len();
1006+
self.bind_results(sig_results_length, results);
10051007
uwriteln!(self.src, "{}({});", self.callee, operands.join(", "));
1008+
1009+
if let Some(prefix) = self.tracing_prefix {
1010+
let to_result_string = self.intrinsic(Intrinsic::ToResultString);
1011+
uwriteln!(
1012+
self.src,
1013+
"console.trace(`{prefix} return {}`);",
1014+
if sig_results_length > 0 || !results.is_empty() {
1015+
format!("result=${{{to_result_string}(ret)}}")
1016+
} else {
1017+
"".to_string()
1018+
}
1019+
);
1020+
}
10061021
}
10071022

10081023
Instruction::CallInterface { func } => {
1024+
let results_length = func.results.len();
10091025
if self.err == ErrHandling::ResultCatchHandler {
10101026
let err_payload = self.intrinsic(Intrinsic::GetErrorPayload);
10111027
uwriteln!(
@@ -1022,10 +1038,23 @@ impl Bindgen for FunctionBindgen<'_> {
10221038
);
10231039
results.push("ret".to_string());
10241040
} else {
1025-
self.bind_results(func.results.len(), results);
1041+
self.bind_results(results_length, results);
10261042
uwriteln!(self.src, "{}({});", self.callee, operands.join(", "));
10271043
}
10281044

1045+
if let Some(prefix) = self.tracing_prefix {
1046+
let to_result_string = self.intrinsic(Intrinsic::ToResultString);
1047+
uwriteln!(
1048+
self.src,
1049+
"console.trace(`{prefix} return {}`);",
1050+
if results_length > 0 || !results.is_empty() {
1051+
format!("result=${{{to_result_string}(ret)}}")
1052+
} else {
1053+
"".to_string()
1054+
}
1055+
);
1056+
}
1057+
10291058
// after a high level call, we need to deactivate the component resource borrows
10301059
if self.cur_resource_borrows.len() > 0 {
10311060
let resource_symbol = self.intrinsic(Intrinsic::ResourceSymbol);

crates/js-component-bindgen/src/intrinsics.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pub enum Intrinsic {
3131
ToInt32,
3232
/// Implementation of https://tc39.es/ecma262/#sec-toint8.
3333
ToInt8,
34+
ToResultString,
3435
/// Implementation of https://tc39.es/ecma262/#sec-tostring.
3536
ToString,
3637
/// Implementation of https://tc39.es/ecma262/#sec-touint16.
@@ -96,6 +97,19 @@ pub fn render_intrinsics(
9697
const hasOwnProperty = Object.prototype.hasOwnProperty;
9798
"),
9899

100+
Intrinsic::ToResultString => output.push_str("
101+
function toResultString(obj) {
102+
return JSON.stringify(obj, (_, v) => {
103+
if (v && Object.getPrototypeOf(v) === Uint8Array.prototype) {
104+
return `[${v[Symbol.toStringTag]} (${v.byteLength})]`;
105+
} else if (typeof v === 'bigint') {
106+
return v.toString();
107+
}
108+
return v;
109+
});
110+
}
111+
"),
112+
99113
Intrinsic::GetErrorPayload => {
100114
let hop = Intrinsic::HasOwnProperty.name();
101115
uwrite!(output, "
@@ -116,10 +130,10 @@ pub fn render_intrinsics(
116130
}
117131
"),
118132

119-
Intrinsic::DataView => output.push_str("
120-
let dv = new DataView(new ArrayBuffer());
121-
const dataView = mem => dv.buffer === mem.buffer ? dv : dv = new DataView(mem.buffer);
122-
"),
133+
Intrinsic::DataView => output.push_str("
134+
let dv = new DataView(new ArrayBuffer());
135+
const dataView = mem => dv.buffer === mem.buffer ? dv : dv = new DataView(mem.buffer);
136+
"),
123137

124138
Intrinsic::FetchCompile => if !no_nodejs_compat {
125139
output.push_str("
@@ -396,6 +410,7 @@ impl Intrinsic {
396410
Intrinsic::ToInt16 => "toInt16",
397411
Intrinsic::ToInt32 => "toInt32",
398412
Intrinsic::ToInt8 => "toInt8",
413+
Intrinsic::ToResultString => "toResultString",
399414
Intrinsic::ToString => "toString",
400415
Intrinsic::ToUint16 => "toUint16",
401416
Intrinsic::ToUint32 => "toUint32",

crates/js-component-bindgen/src/transpile_bindgen.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ pub struct TranspileOpts {
5656
/// Disable verification of component Wasm data structures when
5757
/// lifting as a production optimization
5858
pub valid_lifting_optimization: bool,
59+
/// Whether or not to emit `tracing` calls on function entry/exit.
60+
pub tracing: bool,
5961
}
6062

6163
struct JsBindgen<'a> {
@@ -736,6 +738,11 @@ impl<'a> Instantiator<'a, '_> {
736738
self.bindgen(
737739
nparams,
738740
false,
741+
if import_name.is_empty() {
742+
None
743+
} else {
744+
Some(import_name)
745+
},
739746
&callee_name,
740747
options,
741748
func,
@@ -850,6 +857,7 @@ impl<'a> Instantiator<'a, '_> {
850857
&mut self,
851858
nparams: usize,
852859
this_ref: bool,
860+
module_name: Option<&str>,
853861
callee: &str,
854862
opts: &CanonicalOptions,
855863
func: &Function,
@@ -888,6 +896,26 @@ impl<'a> Instantiator<'a, '_> {
888896
}
889897
uwriteln!(self.src.js, ") {{");
890898

899+
let tracing_prefix = format!(
900+
"[module=\"{}\", function=\"{}\"]",
901+
module_name.unwrap_or("<no module>"),
902+
func.name
903+
);
904+
905+
if self.gen.opts.tracing {
906+
let event_fields = func
907+
.params
908+
.iter()
909+
.enumerate()
910+
.map(|(i, (name, _ty))| format!("{name}=${{arguments[{i}]}}"))
911+
.collect::<Vec<String>>();
912+
uwriteln!(
913+
self.src.js,
914+
"console.trace(`{tracing_prefix} call {}`);",
915+
event_fields.join(", ")
916+
);
917+
}
918+
891919
if self.gen.opts.tla_compat && matches!(abi, AbiVariant::GuestExport) {
892920
let throw_uninitialized = self.gen.intrinsic(Intrinsic::ThrowUninitialized);
893921
uwrite!(
@@ -920,6 +948,11 @@ impl<'a> Instantiator<'a, '_> {
920948
tmp: 0,
921949
params,
922950
post_return: post_return.as_ref(),
951+
tracing_prefix: if self.gen.opts.tracing {
952+
Some(&tracing_prefix)
953+
} else {
954+
None
955+
},
923956
encoding: match opts.string_encoding {
924957
component::StringEncoding::Utf8 => StringEncoding::UTF8,
925958
component::StringEncoding::Utf16 => StringEncoding::UTF16,
@@ -1100,6 +1133,7 @@ impl<'a> Instantiator<'a, '_> {
11001133
func,
11011134
existing_resource_def,
11021135
*ty,
1136+
export_name,
11031137
);
11041138
if let FunctionKind::Constructor(ty)
11051139
| FunctionKind::Method(ty)
@@ -1156,6 +1190,7 @@ impl<'a> Instantiator<'a, '_> {
11561190
func,
11571191
existing_resource_def,
11581192
*ty,
1193+
export_name,
11591194
);
11601195

11611196
if let FunctionKind::Constructor(ty)
@@ -1198,6 +1233,7 @@ impl<'a> Instantiator<'a, '_> {
11981233
func: &Function,
11991234
existing_resource_def: bool,
12001235
ty_func_idx: TypeFuncIndex,
1236+
export_name: &String,
12011237
) {
12021238
let resource_map = self.create_resource_map(func, ty_func_idx);
12031239
match func.kind {
@@ -1247,6 +1283,11 @@ impl<'a> Instantiator<'a, '_> {
12471283
self.bindgen(
12481284
func.params.len(),
12491285
matches!(func.kind, FunctionKind::Method(_)),
1286+
if export_name.is_empty() {
1287+
None
1288+
} else {
1289+
Some(export_name)
1290+
},
12501291
&callee,
12511292
options,
12521293
func,

src/cmd/transpile.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ async function wasm2Js (source) {
6767
* instantiation?: bool,
6868
* map?: Record<string, string>,
6969
* validLiftingOptimization?: bool,
70+
* tracing?: bool,
7071
* noNodejsCompat?: bool,
7172
* tlaCompat?: bool,
7273
* base64Cutoff?: bool,
@@ -107,6 +108,7 @@ export async function transpileComponent (component, opts = {}) {
107108
map: Object.entries(opts.map ?? {}),
108109
instantiation: opts.instantiation || opts.js,
109110
validLiftingOptimization: opts.validLiftingOptimization ?? false,
111+
tracing: opts.tracing ?? false,
110112
noNodejsCompat: !(opts.nodejsCompat ?? true),
111113
noTypescript: opts.noTypescript || false,
112114
tlaCompat: opts.tlaCompat ?? false,
@@ -369,4 +371,4 @@ function asmMangle (name) {
369371
}
370372
}
371373
return name;
372-
}
374+
}

src/jco.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ program.command('transpile')
3737
.option('-O, --optimize', 'optimize the component first')
3838
.option('--no-typescript', 'do not output TypeScript .d.ts types')
3939
.option('--valid-lifting-optimization', 'optimize component binary validations assuming all lifted values are valid')
40+
.option('--tracing', 'emit `tracing` calls on function entry/exit')
4041
.option('-b, --base64-cutoff <bytes>', 'set the byte size under which core Wasm binaries will be inlined as base64', myParseInt)
4142
.option('--tla-compat', 'enables compatibility for JS environments without top-level await support via an async $init promise export')
4243
.option('--no-nodejs-compat', 'disables compatibility in Node.js without a fetch global')

test/cli.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,21 @@ export async function cliTest (fixtures) {
5454
}
5555
});
5656

57+
test('Transpile with tracing', async () => {
58+
try {
59+
const name = 'flavorful';
60+
const { stderr } = await exec(jcoPath, 'transpile', `test/fixtures/components/${name}.component.wasm`, '--name', name, '--map', 'testwasi=./wasi.js', '--tracing', '--base64-cutoff=0', '-o', outDir);
61+
strictEqual(stderr, '');
62+
const source = await readFile(`${outDir}/${name}.js`, 'utf8');
63+
ok(source.includes('function toResultString('));
64+
ok(source.includes('console.trace(`[module="test:flavorful/test", function="f-list-in-record1"] call a'));
65+
ok(source.includes('console.trace(`[module="test:flavorful/test", function="list-of-variants"] return result=${toResultString(ret)}`);'));
66+
}
67+
finally {
68+
await cleanup();
69+
}
70+
});
71+
5772
test('Transpile to JS', async () => {
5873
try {
5974
const name = 'flavorful';

0 commit comments

Comments
 (0)