Calling the print Method

Why is it tradition that when you learn a new language, you have to say "hello world"? For some reason computer scientists have this fascination with "hello world". Every book that tries to teach you a new programming language has one example: print hello world to the screen. I'm going to continue this proud tradition by looking at how SquirrelFish actually executes "hello world".

One of the most interesting things I've learned about VMs is that printing hello world to the screen means you actually have gotten quite a few things working. It's quite an endeavor to successfully implement "hello world" and is a joyous occasion when such an event occurs. Let's take a look at the bytecode for print("hello world"):

[   0] enter
[ 1] mov r3, r0
[ 4] resolve_func r4, r3, print(@id0)
[ 8] mov r5, r1
[ 11] call r3, r3, 2, 14
[ 16] end r3
The moves are actual x86 move instructions. Within the JITed code, resolve_func and call are x86 calls to C methods that do the actual work of the bytecode. Thus, the really interesting bytecodes to look at are resolve_func and call. Let's first take a look at the bytecode resolve_func which calls the C method cti_op_resolve_func:
Interpreter.cpp::cti_op_resolve_func
do {
base = *iter;
if (base->getPropertySlot(callFrame, ident, slot)) {
JSObject* thisObj = base->toThisObject(callFrame);

// Returns the function pointer to "print" method
JSValuePtr result = slot.getValue(callFrame, ident);

RETURN_PAIR(thisObj, JSValuePtr::encode(result));
}
++iter;
} while (iter != end);

The variable iter starts at the beginning of the scope chain and ends at the end of the current scope chain. Thus we're looking for a property through the scope chain as described in the Ecmascript spec. This case is the simplest since only the global scope exists. However, where does the global scope initially define the property print? Where is print defined? Let's checkout the Global Object:
jsc.cpp
GlobalObject::GlobalObject(const Vector<UString>& arguments)
: JSGlobalObject()
{
putDirectFunction(... "debug"), functionDebug));
putDirectFunction(... "print"), functionPrint));
putDirectFunction(... "quit"), functionQuit));
putDirectFunction(... "gc"), functionGC));
putDirectFunction(... "version"), functionVersion));
putDirectFunction(... "run"), functionRun));
putDirectFunction(... "load"), functionLoad));
putDirectFunction(... "readline"), functionReadline));

*... are parameters passed. Shortened for clarity.

Any references to the property "print" actually refer to the C function "functionPrint. Thus resolve_func returns a point to "functionPrint". Then within the compiled context threaded code, there is a test to see if the resolved method is a JavaScript defined function or a native function. Since this is a "native" function, or one implemented in C, the bytecode call invokes cti_op_call_NotJSFunction. Let's take a look at this function:
Interpreter.cpp
JSValueEncodedAsPointer* Interpreter::cti_op_call_NotJSFunction(STUB_ARGS)
{
// Setup call frame
returnValue = callData.native.function(callFrame, asObject(funcVal), thisValue, argList); // Points to functionPrint
return JSValuePtr::encode(returnValue);
}

jsc.cpp:
JSValuePtr functionPrint(ExecState* exec, JSObject*, JSValuePtr, const ArgList& args)
{
printf("%s", args.at(exec, i).toString(exec).UTF8String().c_str());
putchar('\n');
fflush(stdout);
return jsUndefined();
}

cti_op_call_NotJSFunction finally calls functionPrint. Whew! There is the long road to hello world in a virtual machine.