4.2 Print

Overview

We'll start with the simplest and one of the most commonly used external reference: print. As per the ECMAScript specification, there is no such function called console.log. This function is implementation-specific and therefore is deferred to the user. V8 does provide logging, but that's for the core engine's work. Any user-level logging like the very commonly used console.log is not supported by v8.

Function

Registration

The registration of the print function happens at the startup:
v8::ExternalReference {
function: print.map_fn_to()
},

JS space

Let's see how console.log is implemented in JS space. The function console.log comes from the console class:
const windowOrWorkerGlobalScope = {
// -- CODE OMITTED --
console: util.writable(new Console(core.print)),
// -- CODE OMITTED --
}
class Console {
constructor(printFunc) {
this.#printFunc = printFunc;
this.indentLevel = 0;
this[isConsoleInstance] = true;
const console = Object.create({});
Object.assign(console, this);
return console;
}
// -- CODE OMITTED --
log = (...args) => {
this.#printFunc(
inspectArgs(args, {
...getConsoleInspectOptions(),
indentLevel: this.indentLevel,
}) + "\n",
false,
);
};
// -- CODE OMITTED --
}
printFunc is basically core.print function.
At runtime, when v8 encounters core.print, it already knows that print is registered as an external reference. V8 makes call to the external function.

Rust space

The function print is part of Deno and is implemented in Rust. Here is the code of the print function:
fn print(
scope: &mut v8::HandleScope,
args: v8::FunctionCallbackArguments,
_rv: v8::ReturnValue,
) {
let arg_len = args.length();
assert!((0..=2).contains(&arg_len));
let obj = args.get(0);
let is_err_arg = args.get(1);
let mut is_err = false;
if arg_len == 2 {
let int_val = is_err_arg
.integer_value(scope)
.expect("Unable to convert to integer");
is_err = int_val != 0;
};
let tc_scope = &mut v8::TryCatch::new(scope);
let str_ = match obj.to_string(tc_scope) {
Some(s) => s,
None => v8::String::new(tc_scope, "").unwrap(),
};
if is_err {
eprint!("{}", str_.to_rust_string_lossy(tc_scope));
stdout().flush().unwrap();
} else {
print!("{}", str_.to_rust_string_lossy(tc_scope));
stdout().flush().unwrap();
}
}
This is a simple function. It converts the received object to string and then calls the print! macro for printing to the stdout. Then flushes stdout.
For every call to console.log, v8 eventually makes a call to print.