Rev 54934 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed
% -*- Mode: Noweb; noweb-code-mode: R-mode -*-\documentclass[11pt]{article}\usepackage{hyperref}\usepackage[headings]{fullpage}\usepackage{verbatim}\usepackage{noweb}% This is a minor modification to the new verbatim environment that uses% the same size as noweb's code size and indents the same amount at% noweb's code chunks. I redefine verbatim instead of defining my own% environment since the html converter only seems to understand% verbatim, not new definitions.\makeatletter\addto@hook{\every@verbatim}{\nowebsize\setlength{\leftmargin}{50mm}}\def\verbatim@processline{\hspace{\codemargin}\the\verbatim@line\par}\makeatother% The following try to prevent wasteful page breaks\def\nwendcode{\endtrivlist \endgroup}\let\nwdocspar=\par\pagestyle{noweb}\bibliographystyle{plain}\noweboptions{noidentxref,longchunks,smallcode}\title{A Byte Code Compiler for R}\author{Luke Tierney\\Department of Statistics and Actuarial Science\\University of Iowa}\begin{document}\maketitleThis document presents the current implementation of the byte codecompiler for R. The compiler produces code for a virtual machine thatis then executed by a virtual machine runtime system. The virtualmachine is a stack based machine. Thus instructions for the virtualmachine take arguments off a stack and may leave one or more resultson the stack. Byte code objects consists of an integer vectorrepresenting instruction opcodes and operands, and a generic vectorrepresenting a constant pool. The compiler is implemented almostentirely in R, with just a few support routines in C to managecompiled code objects.The virtual machine instruction set is designed to allow much of theinterpreter internals to be re-used. In particular, for now themechanism for calling functions of all types from compiled coderemains the same as the function calling mechanism for interpretedcode. There are opportunities for efficiency improvements throughusing a different mechanism for calls from compiled functions tocompiled functions, or changing the mechanism for both interpreted andcompiled code; this will be explored in future work.The style used by the compiler for building up code objects isimperative: A code buffer object is created that contains buffers forthe instruction stream and the constant pool. Instructions andconstants are written to the buffer as the compiler processes anexpression tree, and at the end a code object is constructed. A morefunctional design in which each compiler step returns a modified codeobject might be more elegant in principle, but it would be moredifficult to make efficient.A multi-pass compiler in which a first pass produces an intermediaterepresentation, subsequent passes optimize the intermediaterepresentation, and a final pass produces actual code would also beuseful and might be able to produce better code. A future version ofthe compiler may use this approach. But for now to keep things simplea single pass is used.%% **** Some peephole optimization is probably possible, and at least%% **** some constant folding could be done on the bytecode, but more%% **** sophisticated optimizations like inlining or R code would require%% **** a more suitable intermediate representation.%% **** I _think_ conversion from stack-based byte code to register-based%% **** code is reasonably straight forward but I haven't thought it%% **** through thoroughly yet.\section{The compiler interface}The compiler can be used either explicitly by calling certainfunctions to carry out compilations, or implicitly by enablingcompilation to occur automatically at certain points.\subsection{Explicit compilation}The primary functions for explicit compilation are [[compile]],[[cmpfun]], and [[cmpfile]].The [[compile]] function compiles an expression and returns a byte codeobject, which can then be passed to [[eval]]. A simple example is\begin{verbatim}> library(compiler)> compile(quote(1+3))<bytecode: 0x25ba070>> eval(compile(quote(1+3)))[1] 4\end{verbatim}A closure can be compiled using [[cmpfun]]. If the function [[f]] isdefined as\begin{verbatim}f <- function(x) {s <- 0.0for (y in x)s <- s + ys}\end{verbatim}then a compiled version is produced by\begin{verbatim}fc <- cmpfun(f)\end{verbatim}We can then compare the performance of the interpreted and compiledversions:\begin{verbatim}> x <- as.double(1 : 10000000)> system.time(f(x))user system elapsed6.470 0.010 6.483> system.time(fc(x))user system elapsed1.870 0.000 1.865\end{verbatim}A source file can be compiled with [[cmpfile]]. For now, the resultingfile has to then be loaded with [[loadcmp]]. In the future it maymake sense to allow [[source]] to either load a pre-compiled file orto optionally compile while sourcing.\subsection{Implicit compilation}Implicit compilation can be used to compile packages as they areinstalled or for just-in-time (JIT) compilation of functions orexpressions. The mechanism for enabling these is experimental andlikely to change.For now, compilation of packages requires the use of lazy loading andcan be enabled either by calling [[compilePKGS]] with argument[[TRUE]] or by starting R with the environment variable[[R_COMPILE_PKGS]] set to a positive integer value. In a UNIX-likeenvironment, for example, installing a package with\begin{verbatim}env R_COMPILE_PKGS=1 R CMD INSTALL foo.tar.gz\end{verbatim}will compile the functions in the package as they are written to thelazy loading data base.If R is installed from source then the base and required packages canbe compiled on installation using\begin{verbatim}make bytecode\end{verbatim}This does not require setting the [[R_COMPILE_PKGS]] environment variable.JIT compilation can be enabled from within R by calling [[enableJIT]]with a non-negative integer argument or by starting R with theenvironment variable [[R_ENABLE_JIT]] set to a non-negative integer.The possible values of the argument to [[enableJIT]] and theirmeanings are\begin{itemize}\item[0] turn off JIT\item[1] compile closures before they are called the first time\item[2] same as 1, plus compile closures before duplicating (usefulfor packages that store closures in lists, like lattice)\item[3] same as 2, plus compile all [[for()]], [[while()]], and[[repeat()]] loops before executing.\end{itemize}R may initially be somewhat sluggish if JIT is enabled and base andrecommended packages have not been pre-compiled as almost everythingwill initially need some compilation.\section{The basic compiler}This section presents the basic compiler for compiling R expressionsto byte code objects.\subsection{The compiler top level}R expressions consist of function calls, variable references, andliteral constants. To create a byte code object representing an Rexpression the compiler has to walk the expression tree and emit codefor the different node types in encounters. The code emitted maydepend on the environment in which the expression will be evaluated aswell as various compiler option settings.The simplest function in the top level compiler interface is thefunction [[compile]]. This function requires an expression argumentand takes two optional arguments: an environment and a list ofoptions. The default environment is the global environment.<<[[compile]] function>>=compile <- function(e, env = .GlobalEnv, options = NULL) {cenv <- makeCenv(env)cntxt <- make.toplevelContext(cenv, options)cntxt$env <- addCenvVars(cenv, findLocals(e, cntxt))genCode(e, cntxt)}@ %def compileThe supplied environment is converted into a compilation environmentdata structure. This compilation environment and any optionsprovided are then used to construct a compiler context. The function[[genCode]] is then used to generate a byte code object for theexpression and the constructed compilation context.Compilation environments are described in Section\ref{sec:environments} and compiler contexts in Section\ref{sec:contexts}. The [[genCode]] function is defined as<<[[genCode]] function>>=genCode <- function(e, cntxt, gen = NULL) {cb <- make.codeBuf(e)if (is.null(gen))cmp(e, cb, cntxt)elsegen(cb, cntxt)codeBufCode(cb)}@ %def genCode[[genCode]] creates a code buffer, fills the code buffer, and thencalls [[codeBufCode]] to extract and return the byte code object. Inthe most common case [[genCode]] uses the low level recursivecompilation function [[cmp]], described in Section \ref{subsec:cmp},to generate the code. For added flexibility it can be given agenerator function that emits code into the code buffer based on theprovided context. This is used in Section \ref{sec:loops} for ****.\subsection{Basic code buffer interface}Code buffers are used to accumulate the compiled code and relatedconstant values. A code buffer [[cb]] is a list containing a numberof closures used to manipulate the content of the code buffer. Inthis section two closures are used, [[putconst]] and [[putcode]].The closure [[cb$putconst]] is used to enter constants into theconstant pool. It takes a single argument, an arbitrary R object tobe entered into the constant pool, and returns an integer index intothe pool. The [[cb$putcode]] closure takes an instruction opcode andany operands the opcode requires and emits them into the code buffer.The operands are typically constant pool indices or labels, to beintroduced in Section \ref{sec:codebuf}.As an example, the [[GETVAR]] instruction takes one operand, the indexin the constant pool of a symbol. The opcode for this instruction is[[GETVAR.OP]]. The instruction retrieves the symbol from the constantpool, looks up its value in the current environment, and pushes thevalue on the stack. If [[sym]] is a variable with value a symbol,then code to enter the symbol in the constant pool and emit aninstruction to get its value would be<<example of emitting a [[GETVAR]] instruction>>=ci <- cb$putconst(sym)cb$putcode(GETVAR.OP, ci)@ %defThe complete code buffer implementation is given in Section\ref{sec:codebuf}.\subsection{The recursive code generator}\label{subsec:cmp}The function [[cmp]] is the basic code generation function. Itrecursively traverses the expression tree and emits code as it visitseach node in the tree.Before generating code for an expression the function [[cmp]] attemptsto determine the value of the expression by constant folding using thefunction [[constantFold]]. If constant folding is successful then[[contantFold]] returns a named list containing a [[value]] element.Otherwise it returns [[NULL]]. If constant folding is successful,then the result is compiled as a constant. Otherwise, the standardcode generation process is used.%% **** comment on alternative of doing constant folding as an%% **** optimization on the butecode or an intermediate representation?In the interpreter there are four types of objects that are nottreated as constants, i.e. as evaluating to themselves: function callsof type [["language"]], variable references of type [["symbol"]],promises, and byte code objects. Neither promises nor byte codeobjects should appear as literals in code so an error is signaled forthose. The language, symbol, and constant cases are each handled bytheir own code generators.%% **** promises do appear in the expressions generated by the%% **** interpreter for evaluating complex assignment epxressions<<generate code for expression [[e]]>>=if (typeof(e) == "language")cmpCall(e, cb, cntxt)else if (typeof(e) == "symbol")cmpSym(e, cb, cntxt)else if (typeof(e) == "bytecode")cntxt$stop(gettext("cannot compile byte code literals in code"),cntxt)else if (typeof(e) == "promise")cntxt$stop(gettext("cannot compile promise literals in code"),cntxt)elsecmpConst(e, cb, cntxt)@The function [[cmp]] is then defined as<<[[cmp]] function>>=cmp <- function(e, cb, cntxt) {ce <- constantFold(e, cntxt)if (is.null(ce)) {<<generate code for expression [[e]]>>}elsecmpConst(ce$value, cb, cntxt)}@ %def cmpThe call code generator [[cmpCall]] will recursively call [[cmp]].%% **** should promises/byte code produce compiler errors or runtime errors??\subsection{Compiling constant expressions}The constant code generator [[cmpConst]] is the simplest of the threegenerators. A simplified generator can be defined as<<simplified [[cmpConst]] function>>=cmpConst <- function(val, cb, cntxt) {ci <- cb$putconst(val)cb$putcode(LDCONST.OP, ci)if (cntxt$tailcall) cb$putcode(RETURN.OP)}@ %def cmpConstThis function enters the constant in the constant pool using theclosure [[cb$putconst]]. The value returned by this closure is anindex for the constant in the constant pool. Then the code generatoremits an instruction to load the constant at the specified constantpool index and push it onto the stack. If the expression appears intail position then a [[RETURN]] instruction is emitted as well.%% **** explain tail position here??Certain constant values, such as [[TRUE]], [[FALSE]], and [[NULL]]appear very often in code. It may be useful to provide and use specialinstructions for loading these. The resulting code will have slightlysmaller constant pools and may be a little faster, though thedifference is likely to be small. A revised definition of[[cmpConst]] that makes use of instructions for loading theseparticular values is given by<<[[cmpConst]] function>>=cmpConst <- function(val, cb, cntxt) {if (identical(val, NULL))cb$putcode(LDNULL.OP)else if (identical(val, TRUE))cb$putcode(LDTRUE.OP)else if (identical(val, FALSE))cb$putcode(LDFALSE.OP)else {ci <- cb$putconst(val)cb$putcode(LDCONST.OP, ci)}if (cntxt$tailcall) cb$putcode(RETURN.OP)}@ %def cmpConstIt might be useful to handle other constants in a similar way, such as[[NA]] or small integer values; this may be done in the future.%% **** check out if small integers is worth doing.%% **** mention peephole optimization as alternativeIdeally the implementation should be able to mark the values in theconstant pool of a byte code object as read-only by setting the[[NAMED]] field to 2, but experience in testing shows that there areseveral packages in the wild that assume that an expression [[TRUE]],for example, appearing in code will result in a freshly allocatedvalue that can be freely modified in [[.C]] calls. It would be good toeducate users not to do this, but for now the implementationduplicates all values as they are retrieved from the constant pool.\subsection{Compiling variable references}The function [[cmpSym]] handles compilation of variablereferences. For standard variables this involves entering the symbolin the constant pool, emitting code to look up the value of thevariable at the specified constant pool location in the currentenvironment, and, if necessary, emitting a [[RETURN]] instruction.In addition to standard variables there is the ellipsis variable[[...]] and the accessors [[..1]], [[..2]], and so on that need to beconsidered. The ellipsis variable can only appear as an argument infunction calls, so [[cmp]], like the interpreter [[eval]] itself,should not encounter it. The interpreter signals an error if it doesencounter a [[...]] variable, and the compiler emits code that doesthe same at runtime. The compiler also emits a warning at compiletime. Variables representing formal parameters may not have valuesprovided in their calls, i.e. may have missing values. In some casesthis should signal an error; in others the missing value can be passedon (for example in expressions of the form [[x[]]]). To support this,[[cmpSym]] takes an optional argument for allowing missing argumentvalues.<<[[cmpSym]] function>>=cmpSym <- function(sym, cb, cntxt, missingOK = FALSE) {if (sym == "...") {notifyWrongDotsUse("...", cntxt)cb$putcode(DOTSERR.OP)}else if (is.ddsym(sym)) {<<emit code for [[..n]] variable references>>}else {<<emit code for standard variable references>>}}@ %def cmpSymReferences to [[..n]] variables are also only appropriate when a[[...]] variable is available, so a warning is given if that is notthe case. The virtual machine provides instructions [[DDVAL]] and[[DDVAL_MISSOK]] for the case where missing arguments are not allowedand for the case where they are, and the appropriate instruction isused based on the [[missingOK]] argument to [[cmpSym]].<<emit code for [[..n]] variable references>>=if (! findLocVar("...", cntxt))notifyWrongDotsUse(sym, cntxt)ci <- cb$putconst(sym)if (missingOK)cb$putcode(DDVAL_MISSOK.OP, ci)elsecb$putcode(DDVAL.OP, ci)if (cntxt$tailcall) cb$putcode(RETURN.OP)@ %defThere are also two instructions available for obtaining the value of ageneral variable from the current environment, one that allows missingvalues and one that does not.<<emit code for standard variable references>>=if (! findVar(sym, cntxt))notifyUndefVar(sym, cntxt)ci <- cb$putconst(sym)if (missingOK)cb$putcode(GETVAR_MISSOK.OP, ci)elsecb$putcode(GETVAR.OP, ci)if (cntxt$tailcall) cb$putcode(RETURN.OP)@ %defFor now, these instructions only take an index in the constant poolfor the symbol as operands, not any information about where thevariable can be found within the environment. This approach toobtaining the value of variables requires a search of the currentenvironment for every variable reference. In a less dynamic languageit would be possible to compute locations of variable bindings withinan environment at compile time and to choose environmentrepresentations that allow constant time access to any variable'svalue. Since bindings in R can be added or removed at runtime thiswould require a semantic change that would need some form ofdeclaration to make legitimate. Another approach that may be worthexploring is some sort of caching mechanism in which the location ofeach variable is stored when it is first found by a full search, andthat cached location is used until an event occurs that forcesflushing of the cache. If such events are rare, as they typically are,then this may be effective.%% **** need to look into caching strategies%% **** looks like a simple cache of the local frame speeds up sum and%% **** Neal's em by about 10% (just lookup, not assignment -- with%% **** assignment should be a bit better)%% **** Avoid using ftype variable in bcEval. Could just look at the%% **** fun on the stack, or use the intstack instead.%% **** Is it really necessary for bcEval to save/restore stack tops?%% **** Shouldn't that happen automatically?%% **** Is it possible to have closure calling stay in the same bc?%% **** maybe at least for promises?\subsection{Compiling function calls}Conceptually, the R function calling mechanism uses lazy evaluation ofarguments. Thus calling a function involves three steps:\begin{itemize}\item finding the function to call\item packaging up the argument expressions into deferred evaluationobjects, or promises\item executing the call\end{itemize}Code for this process is generated by the function [[cmpCall]]. Asimplified version is defined as<<simplified [[cmpCall]] function>>=cmpCall <- function(call, cb, cntxt) {cntxt <- make.callContext(cntxt, call)fun <- call[[1]]args <- call[-1]if (typeof(fun) == "symbol")cmpCallSymFun(fun, args, call, cb, cntxt)elsecmpCallExprFun(fun, args, call, cb, cntxt)}@ %def cmpCallCall expressions in which the function is represented by a symbol arecompiled by [[cmpCallSymFun]]. This function emits a [[GETFUN]]instruction and then compiles the arguments.<<[[cmpCallSymFun]] function>>=cmpCallSymFun <- function(fun, args, call, cb, cntxt) {ci <- cb$putconst(fun)cb$putcode(GETFUN.OP, ci)<<compile arguments and emit [[CALL]] instruction>>}@ %def cmpCallSymFunThe [[GETFUN]] instruction takes a constant pool index of the symbolas an operand, looks for a function binding to the symbol in thecurrent environment, places it on the stack, and prepares the stackfor handling function call arguments.%% **** need a fun cache and a var cache???Argument compilation is carried out by the function [[cmpCallArgs]],presented in Section \ref{subsec:callargs}, and is followed byemitting code to execute the call and, if necessary, return a result.<<compile arguments and emit [[CALL]] instruction>>=cmpCallArgs(args, cb, cntxt)ci <- cb$putconst(call)cb$putcode(CALL.OP, ci)if (cntxt$tailcall) cb$putcode(RETURN.OP)@ %defThe call expression itself is stored in the constant pool and isavailable to the [[CALL]] instruction.Calls in which the function is represented by an expression other thana symbol are handled by [[cmpCallExprFun]]. This emits code toevaluate the expression, leaving the value in the stack, and thenemits a [[CHECKFUN]] instruction. This instruction checks that thevalue on top of the stack is a function and prepares the stack forreceiving call arguments. Generation of argument code and the[[CALL]] instruction are handled as for symbol function calls.<<[[cmpCallExprFun]] function>>=cmpCallExprFun <- function(fun, args, call, cb, cntxt) {ncntxt <- make.nonTailCallContext(cntxt)cmp(fun, cb, ncntxt)cb$putcode(CHECKFUN.OP)<<compile arguments and emit [[CALL]] instruction>>}@ %def cmpCallExprFunThe actual definition of [[cmpCall]] is a bit more complex than thesimplified one given above:<<[[cmpCall]] function>>=cmpCall <- function(call, cb, cntxt) {cntxt <- make.callContext(cntxt, call)fun <- call[[1]]args <- call[-1]if (typeof(fun) == "symbol") {if (! tryInline(call, cb, cntxt)) {<<check the call to a symbol function>>cmpCallSymFun(fun, args, call, cb, cntxt)}}else {<<hack for handling [[break()]] and [[next()]] expressions>>cmpCallExprFun(fun, args, call, cb, cntxt)}}@ %def cmpCallThe main addition is the use of a [[tryInline]] function which triesto generate more efficient code for particular functions. Thisfunction returns [[TRUE]] if it has handled code generation and[[FALSE]] if it has not. Code will be generated by the inlinemechanism if inline handlers for the particular function are availableand the optimization level permits their use. Details of the inliningmechanism are given in Section \ref{sec:inlining}.In addition to the inlining mechanism, some checking of the call iscarried out for symbol calls. The checking code is<<check the call to a symbol function>>=if (findLocVar(fun, cntxt))notifyLocalFun(fun, cntxt)else {def <- findFunDef(fun, cntxt)if (is.null(def))notifyUndefFun(fun, cntxt)elsecheckCall(def, call, function(w) notifyBadCall(w, cntxt))}@and [[checkCall]] is defined as<<[[checkCall]] function>>=## **** clean up to use tryCatch## **** figure out how to handler multi-line deparses## **** e.g. checkCall(`{`, quote({}))## **** better design would capture error object, wrap it up, and pass it oncheckCall <- function(def, call, signal = warning) {if (typeof(def) %in% c("builtin", "special"))def <- args(def)if (typeof(def) != "closure" || any.dots(call))NAelse {old <-options()$show.error.messagesif (is.null(old)) old <- TRUEoptions(show.error.messages=FALSE)msg <- try({match.call(def, call); NULL})options(show.error.messages=old)if (! is.null(msg)) {msg <- sub("\n$", "", sub("^E.*: ", "", msg))emsg <- gettextf("possible error in '%s': %s",deparse(call, 20)[1], msg)if (! is.null(signal)) signal(emsg)FALSE}else TRUE}}@ %def checkCallFinally, for calls where the function is an expression a hack iscurrently needed for dealing with the way the parser currently parsesexpressions of the form [[break()]] and [[next()]]. To be able tocompile as many [[break]] and [[next]] calls as possible as simple[[GOTO]] instructions these need to be handled specially to avoidplacing things on the stack. A better solution would probably be tomodify the parser to make expressions of the form [[break()]] besyntax errors.<<hack for handling [[break()]] and [[next()]] expressions>>=## **** this hack is needed for now because of the way the## **** parser handles break() and next() callsif (typeof(fun) == "language" && typeof(fun[[1]]) == "symbol" &&as.character(fun[[1]]) %in% c("break", "next"))return(cmp(fun, cb, cntxt))@\subsection{Compiling call arguments}\label{subsec:callargs}Function calls can contain four kinds of arguments:\begin{itemize}\item missing arguments\item [[...]] arguments\item general expressions\end{itemize}In the first and third cases the arguments can also be named. Theargument compilation function [[cmpCallArgs]] loops over the argumentlists and handles each of the three cases, in addition to signalingerrors for arguments that are literal bytecode or promise objects:<<[[cmpCallArgs]] function>>=cmpCallArgs <- function(args, cb, cntxt) {names <- names(args)pcntxt <- make.promiseContext(cntxt)for (i in seq_along(args)) {a <- args[[i]]n <- names[[i]]<<compile missing argument>><<compile [[...]] argument>><<signal an error for promise or bytecode argument>><<compile a general argument>>}}@ %def cmpCallArgsThe missing argument case is handled by<<compile missing argument>>=if (missing(a)) { ## better test for missing??cb$putcode(DOMISSING.OP)cmpTag(n, cb)}@ %defComputations on the language related to missing arguments are tricky.The use of [[missing]] is a little odd, but for now at least it doeswork.An ellipsis argument [[...]] is handled by the [[DODOTS]] instruction:<<compile [[...]] argument>>=else if (is.symbol(a) && a == "...") {if (! findLocVar("...", cntxt))notifyWrongDotsUse("...", cntxt)cb$putcode(DODOTS.OP)}@ %defA warning is issued if no [[...]] argument is visible.As in [[cmp]], errors are signaled for literal bytecode or promisevalues as arguments.<<signal an error for promise or bytecode argument>>=else if (typeof(a) == "bytecode")cntxt$stop(gettext("cannot compile byte code literals in code"),cntxt)else if (typeof(a) == "promise")cntxt$stop(gettext("cannot compile promise literals in code"),cntxt)@ %defA general non-constant argument expression is compiled to a separatebyte code object which is stored in the constant pool. The compilerthen emits a [[MAKEPROM]] instruction that uses the stored codeobject. Promises are not needed for literal constant arguments asthese are self-evaluating. Within the current implementation both theevaluation process and use of [[substitute]] will work properly ifconstants are placed directly in the argument list rather than beingwrapped in promises. This could also be done in the interpreter,though the benefit is less clear as a runtime determination of whetheran argument is a constant would be needed. This may still be cheapenough compared to the cost of allocating a promise to be worth doing.Constant folding in [[cmp]] may also produce more constants, butpromises are needed in this case in order for [[substitute]] to workproperly. These promises could be created as evaluated promises,though it is not clean how much this would gain.<<compile a general argument>>=else {if (is.symbol(a) || typeof(a) == "language") {ci <- cb$putconst(genCode(a, pcntxt))cb$putcode(MAKEPROM.OP, ci)}elsecmpConstArg(a, cb, cntxt)cmpTag(n, cb)}@ %def%% **** look into using evaluated promises for constant folded arguments%% **** then we would use a variant of this:% else {% ca <- constantFold(a, cntxt)% if (is.null(ca)) {% if (is.symbol(a) || typeof(a) == "language") {% ci <- cb$putconst(genCode(a, pcntxt))% cb$putcode(MAKEPROM.OP, ci)% }% else% cmpConstArg(a, cb, cntxt)% }% else% cmpConstArg(ca$value, cb, cntxt)% cmpTag(n, cb)% }For calls to closures the [[MAKEPROM]] instruction retrieves the codeobject, creates a promise from the code object and the currentenvironment, and pushes the promise on the argument stack. For callsto functions of type [[BULTIN]] the [[MAKEPROM]] instruction actuallyexecutes the code object in the current environment and pushes theresulting value on the stack. For calls to functions of type[[SPECIAL]] the [[MAKEPROM]] instruction does nothing as these calls useonly the call expression.Constant arguments are compiled by [[cmpConstArg]]. Again there arespecial instructions for the common special constants [[NULL]],[[TRUE]], and [[FALSE]].<<[[cmpConstArg]]>>=cmpConstArg <- function(a, cb, cntxt) {if (identical(a, NULL))cb$putcode(PUSHNULLARG.OP)else if (identical(a, TRUE))cb$putcode(PUSHTRUEARG.OP)else if (identical(a, FALSE))cb$putcode(PUSHFALSEARG.OP)else {ci <- cb$putconst(a)cb$putcode(PUSHCONSTARG.OP, ci)}}@ %def cmpConstArgCode to install names for named arguments is generated by [[cmpTag]]:<<[[cmpTag]] function>>=cmpTag <- function(n, cb) {if (! is.null(n) && n != "") {ci <- cb$putconst(as.name(n))cb$putcode(SETTAG.OP, ci)}}@ %def cmpTagThe current implementation allocates a linked list of call arguments,stores tags in the list cells, and allocates promises. Alternativeimplementations that avoid some or all allocation are worth exploring.Also worth exploring is having an instruction specifically for calls thatdo not require matching of named arguments to formal arguments, sincecases that use only order of arguments, not names, are quite commonand are known at compile time. In the case of calls to functions withdefinitions known at compile time matching of named arguments toformal ones could also be done at compile time.\subsection{Discussion}The fremework presented in this section, together with some supportfunctions, is actually able to compile any legal R code. But this issomewhat deceptive. The R implementation, and the [[CALL]] opcode,support three kinds of functions: closures (i.e. R-level functions),primitive functions of type [[BUILTIN]], and primitive functions oftype [[SPECIAL]]. Primitives of type [[BUILTIN]] always evaluatetheir arguments in order, so creating promises is not necessary and infact the [[MAKEPROM]] instruction does not do so --- if the functionto be called is a [[BUILTIN]] then [[MAKEPROM]] runs the code forcomputing the argument in the current environment and pushes the valueon the stack. On the other hand, primitive functions of type[[SPECIAL]] use the call expression and evaluate bits of it asneeded. As a result, they will be running interpreted code. Sincecore functions like the sequencing function [[{]] and the conditionalevaluation function [[if]] are of type [[SPECIAL]] this means mostnon-trivial code will be run by the standard interpreter. This willbe addressed bi defining inlining rules that allow functions like[[{]] and [[if]] to be compiled properly.\section{The code buffer}\label{sec:codebuf}The code buffer is a collection of closures that accumulate code andconstants in variables in their defining environment. For a codebuffer [[cb]] the closures [[cb$putcode]] and [[cb$putconst]] write aninstruction sequence and a constant, respectively, into the codebuffer. The closures [[cb$code]] and [[cb$consts]] extract the codevector and the constant pool.The function [[make.codeBuf]] creates a set of closures for managingthe instruction stream buffer and the constant pool buffer and returnsa list of these closures for use by the compilation functions. Inaddition, the expression to be compiled into the code buffer is storedas the first constant in the constant pool; this can be used toretrieve the source code for a compiled expression.<<[[make.codeBuf]] function>>=make.codeBuf <- function(expr) {<<instruction stream buffer implementation>><<constant pool buffer implementation>><<label management interface>>cb <- list(code = getcode,const = getconst,putcode = putcode,putconst = putconst,makelabel = makelabel,putlabel = putlabel,patchlabels = patchlabels)cb$putconst(expr) ## insert expression as first constant.cb}@ %def make.codeBufThe instruction stream buffer uses a list structure and a count ofelements in use, and doubles the size of the list to make room for newcode when necessary. By convention the first entry is a byte codeversion number; if the interpreter sees a byte code version number itcannot handle then it falls back to interpreting the uncompiledexpression. The doubling strategy is needed to avoid quadraticcompilation times for large instruction streams.<<instruction stream buffer implementation>>=codeBuf <- list(.Internal(bcVersion()))codeCount <- 1putcode <- function(...) {new <- list(...)newLen <- length(new)while (codeCount + newLen > length(codeBuf))codeBuf <<- c(codeBuf, vector("list", length(codeBuf)))codeBuf[(codeCount + 1) : (codeCount + newLen)] <<- newcodeCount <<- codeCount + newLen}getcode <- function() as.integer(codeBuf[1 : codeCount])@The constant pool is accumulated into a list buffer. The zero-basedindex of the constant in the pool is returned by the insertionfunction. Values are only entered once; if a value is already in thepool, as determined by [[identical]], its existing index is returned.Again a size-doubling strategy is used for the buffer. [[.Internal]]functions are used both for performance reasons and to preventduplication of the constants.<<constant pool buffer implementation>>=constBuf <- vector("list", 1)constCount <- 0putconst <- function(x) {if (constCount == length(constBuf))constBuf <<- .Internal(growconst(constBuf))i <- .Internal(putconst(constBuf, constCount, x))if (i == constCount)constCount <<- constCount + 1i}getconst <- function().Internal(getconst(constBuf, constCount))@ %defLabels are used for identifying targts for branching instruction. Thelabel management interface creates new labels with [[makelabel]] ascharacter strings that are unique within the buffer. These labels canthen be included as operands in branching instructions. The[[putlabel]] function records the current code position as the valueof the label.<<label management interface>>=idx <- 0labels <- vector("list")makelabel <- function() { idx <<- idx + 1; paste("L", idx, sep="") }putlabel <- function(name) labels[[name]] <<- codeCount@Once code generation is complete the symbolic labels in the codestream need to be converted to numerical offset values. This is doneby [[patchlabels]]. Labels can appear directly in the instructionstream os in lists that have been placed in the instruction stream;this is used for the [[SWITCH]] instruction.<<label management interface>>=patchlabels <- function() {offset <- function(lbl) {if (is.null(labels[[lbl]]))stop(gettextf("no offset recorded for label \"%s\"", lbl),domain = NA)labels[[lbl]]}for (i in 1 : codeCount) {v <- codeBuf[[i]]if (is.character(v))codeBuf[[i]] <<- offset(v)else if (typeof(v) == "list") {off <- as.integer(lapply(v, offset))ci <- putconst(off)codeBuf[[i]] <<- ci}}}@ %defThe contents of the code buffer is extracted into a code object bycalling [[codeBufCode]]:<<[[codeBufCode]] function>>=codeBufCode <- function(cb) {cb$patchlabels().Internal(mkCode(cb$code(), cb$const()))}@ %def codeBufCode\section{Compiler contexts}\label{sec:contexts}The compiler context object [[cntxt]] carries along information aboutwhether the expression appears in tail position and should be followedby a return or, whether the result is ignored, or whether theexpression is contained in a loop. The context object also containscurrent compiler option settings as well as functions used to issuewarnings or signal errors.\subsection{Top level contexts}Tho level compiler functions start by creating a top level context.The constructor for top level contexts takes as arguments the currentcompilation environment, described in Section \ref{sec:environments},and a list of option values used to override default option settings.Top level expressions are assumed to be in tail position, so the[[tailcall]] field is initialized as [[TRUE]]. The [[needRETURNJMP]]specifies whether a call to the [[return]] function can use the[[RETURN]] instruction or has to use a [[longjmp]] via the[[RETURNJMP]] instruction. Initially using a simple [[RETURN]] issafe; this is set set to [[TRUE]] when compiling promises ad certainloops where [[RETURNJMP]] is needed.<<[[make.toplevelContext]] function>>=make.toplevelContext <- function(cenv, options = NULL)structure(list(tailcall = TRUE,needRETURNJMP = FALSE,env = cenv,optimize = getCompilerOption("optimize", options),suppressAll = getCompilerOption("suppressAll", options),suppressUndefined = getCompilerOption("suppressUndefined",options),call = NULL,stop = function(msg, cntxt)stop(simpleError(msg, cntxt$call)),warn = function(x, cntxt) cat(paste("Note:", x, "\n"))),class = "compiler_context")@ %def make.toplevelContextErrors are signaled using a version of [[stop]] that uses the currentcall in the compilation context. The default would be to use the callin the compiler code where the error was raised, and that would not bemeaningful to the end user. Ideally [[warn]] should do somethingsimilar and also use the condition system, but for now it just printsa simple message to standard output.%% **** look into adding source info to errors/warnings%% **** comment on class of context object\subsection{Other compiler contexts}The [[cmpCall]] function creates a new context for each call itcompiles. The new context is the current context with the [[call]]entry replaced by the current call --- this is be useful for issuingmeaningful warning and error messages.<<[[make.callContext]] function>>=make.callContext <- function(cntxt, call) {cntxt$call <- callcntxt}@ %def make.callContextNon-tail-call contexts are used when a value is being computed for usein a subsequent computation. The constructor returns a new contextthat is the current context with the tailcall field set to [[FALSE]].<<[[make.nonTailCallContext]] function>>=make.nonTailCallContext <- function(cntxt) {cntxt$tailcall <- FALSEcntxt}@ %def make.nonTailCallContextA no value context is used in cases where the computed value will beignored. For now this is identical to a non-tail-call context, but itmay eventually be useful to distinguish the two situations. This isused mainly for expressions other than the final one in [[{]] callsand for compiling the bodies of loops.%% **** can avoid generating push/pop pairs if novalue = TRUE is used%% **** might simplify tailcall/RETURN stuff??<<[[make.noValueContext]] function>>=make.noValueContext <- function(cntxt) {cntxt$tailcall <- FALSEcntxt}@ %def make.noValueContextThe compiler context for compiling a function is a new toplevelcontext using the function environment and the current compileroptions settings.%% **** copy other compiler options; maybe cntxt$options$optimize??<<[[make.functionContext]] function>>=make.functionContext <- function(cntxt, forms, body) {nenv <- funEnv(forms, body, cntxt)ncntxt <- make.toplevelContext(nenv)ncntxt$optimize <- cntxt$optimizencntxt$suppressAll <- cntxt$suppressAllncntxt$suppressUndefined <- cntxt$suppressUndefinedncntxt}@ %def make.functionContextThe context for compiling the body of a loop is a no value contextwith the loop information available.<<[[make.loopContext]] function>>=make.loopContext <- function(cntxt, loop.label, end.label) {ncntxt <- make.noValueContext(cntxt)ncntxt$loop <- list(loop = loop.label, end = end.label, gotoOK = TRUE)ncntxt}@ %def make.loopContextThe initial loop context allows [[break]] and [[next]] calls to beimplemented as [[GOTO]] instructions. This is OK for calls that arein top level position relative to the loop. Calls that occur inpromises or in other contexts where the stack has changed from theloop top level state need stack unwinding and cannot be implemented as[[GOTO]] instructions. These should should be compiled with contextsthat have the [[loop$gotoOK]] field set to [[FALSE]]. The promisecontext does this for promises and the argument context for othersettings. The promise context also sets [[needRETURNJMP]] to [[TRUE]]since a [[return]] call that is triggered by forcing a promiserequires a [[longjmp]] to return from the appropriate function.<<[[make.argContext]] function>>=make.argContext <- function(cntxt) {cntxt$tailcall <- FALSEif (! is.null(cntxt$loop))cntxt$loop$gotoOK <- FALSEcntxt}@ %def make.argContext<<[[make.promiseContext]] function>>=make.promiseContext <- function(cntxt) {cntxt$tailcall <- TRUEcntxt$needRETURNJMP <- TRUEif (! is.null(cntxt$loop))cntxt$loop$gotoOK <- FALSEcntxt}@ %def make.promiseContext%% pull out gotoOK chunk\subsection{Compiler options}Default compiler options are maintained in an environment. For now,the supported options are [[optimize]], which is initialized to level2, and two options for controlling compiler messages. The[[suppressAll]] option, if [[TRUE]], suppresses all notifications.The [[suppressUndefined]] option can be [[TRUE]] to suppress allnotifications about undefined variables and functions, or it can be acharacter vector of the names of variables for which warnings shouldbe suppressed.<<compiler options data base>>=compilerOptions <- new.env(hash = TRUE, parent = emptyenv())compilerOptions$optimize <- 2compilerOptions$suppressAll <- FALSEcompilerOptions$suppressUndefined <-c(".Generic", ".Method", ".Random.seed", ".self")@ %def compilerOptionsOptions are retrieved with the [[getCompilerOption]] function.<<[[getCompilerOption]] function>>=getCompilerOption <- function(name, options = NULL) {if (name %in% names(options))options[[name]]elseget(name, compilerOptions)}@ %def getCompilerOptionThe [[suppressAll]] function determines whether a context has its[[supressAll]] property set to [[TRUE]].<<[[suppressAll]] function>>=suppressAll <- function(cntxt)identical(cntxt$suppressAll, TRUE)@ %def suppressAllThe [[suppressUndef]] function determines whether undefined variableor function definition notifications for a particular variable shouldbe suppressed in a particular compiler context.<<[[suppressUndef]] function>>=suppressUndef <- function(name, cntxt) {if (identical(cntxt$suppressAll, TRUE))TRUEelse {suppress <- cntxt$suppressUndefinedif (is.null(suppress))FALSEelse if (identical(suppress, TRUE))TRUEelse if (is.character(suppress) && as.character(name) %in% suppress)TRUEelse FALSE}}@ %def suppressUndefAt some point we will need mechanisms for setting default options fromthe interpreter and in package meta-data. A declaration mechanism foradjusting option settings locally will also be needed.\subsection{Compiler notifications}Compiler notifications are currently sent by calling the context's[[warn]] function, which in turn prints a message to standard output.It would be better to use an approach based on the condition system,and this will be done eventually. The use of separate notificationfunctions for each type of issue signaled is a step in this direction.Undefined function and undefined variable notifications are issued by[[notifyUndefFun]] and [[notifyUndefVar]]. These both use[[supprressUndef]] to determine whether the notification should besuppressed in the current context.<<[[notifyUndefFun]] function>>=notifyUndefFun <- function(fun, cntxt) {if (! suppressUndef(fun, cntxt)) {msg <- gettextf("no visible global function definition for '%s'",as.character(fun))cntxt$warn(msg, cntxt)}}@ %def notifyUndefFun<<[[notifyUndefVar]] function>>=notifyUndefVar <- function(var, cntxt) {if (! suppressUndef(var, cntxt)) {msg <- gettextf("no visible binding for global variable '%s'",as.character(var))cntxt$warn(msg, cntxt)}}@ %def notifyUndefVarCodetools currently optionally notifies about use of localfunctions. This is of course not an error but may sometimes be theresult of a mis-spelling. For now the compiler does not notify aboutthese, but this could be changed by redefining [[notifyLocalFun]] .<<[[notifyLocalFun]] function>>=notifyLocalFun <- function(fun, cntxt) {if (! suppressAll(cntxt))NULL}@ %def notifyLocalFunWarnings about possible improper use of [[...]] and [[..n]] variablesare sent by [[notifyWrongDotsUse]].<<[[notifyWrongDotsUse]] function>>=notifyWrongDotsUse <- function(var, cntxt) {if (! suppressAll(cntxt))cntxt$warn(paste(var, "may be used in an incorrect context"), cntxt)}@ %def notifyWrongDotsUseWrong argument count issues are signaled by [[notifyWrongArgCount]].<<[[notifyWrongArgCount]] function>>=notifyWrongArgCount <- function(fun, cntxt) {if (! suppressAll(cntxt))cntxt$warn(gettextf("wrong number of arguments to '%s'",as.character(fun)),cntxt)}@ %def notifyWrongArgCountOther issues with calls that do not match their definitions aresignaled by [[notifyBadCall]]. Ideally these should be broken downmore finely, but that would require some rewriting of the errorsignaling in [[match.call]].<<[[notifyBadCall]] function>>=notifyBadCall <- function(w, cntxt) {if (! suppressAll(cntxt))cntxt$warn(w, cntxt)}@ %def notifyBadCall[[break]] or [[next]] calls that occur in a context where no loop isvisible will most likely result in runtime errors, and[[notifyWrongBreakNext]] is used to signal such cases.<<[[notifyWrongBreakNext]] function>>=notifyWrongBreakNext <- function(fun, cntxt) {if (! suppressAll(cntxt)) {msg <- paste(fun, "may be used in wrong context: no loop is visible")cntxt$warn(msg, cntxt)}}@ %def notifyWrongBreakNextSeveral issues can arise in assignments. For super-assignments atarget variable should be defined; otherwise there will be a runtimewarning.<<[[notifyNoSuperAssignVar]] function>>=notifyNoSuperAssignVar <- function(symbol, cntxt) {if (! suppressAll(cntxt)) {msg <- gettextf("no visible binding for '<<-' assignment to '%s'",as.character(symbol))cntxt$warn(msg, cntxt)}}@ %def notifyNoSuperAssignVarIf the compiler detects an invalid function in a complex assignmentthen this is signaled at compile time; a corresponding error wouldoccur at runtime.%% **** should put function/call into message<<[[notifyBadAssignFun]] function>>=notifyBadAssignFun <- function(fun, cntxt) {if (! suppressAll(cntxt))cntxt$warn(gettext("invalid function in complex assignment"))}@ %def notifyBadAssignFunIn [[switch]] calls it is an error if a character selector argument isused and there are multiple default alternatives. The compilersignals a possible problem with [[notifyMultipleSwitchDefaults]] ifthere are some named cases but more than one unnamed ones.<<[[notifyMultipleSwitchDefaults]] function>>=notifyMultipleSwitchDefaults <- function(ndflt, cntxt)cntxt$warn(gettext("more than one default provided in switch call"),cntxt)@ %def notifyMultipleSwitchDefaults\section{Compilation environments}\label{sec:environments}%% **** lambda lifting/eliminating variables not captured%% **** defer SETVAR until needed; avoid GETVAR if still in register%% **** Could preserve semantics by pre-test to check that fun in env%% **** is inlined one. Would need to make efficient somehow, e.g%% **** increment counter each time one of inlined names is assigned%% **** to and only check when count has canged.At this point the compiler will essentially use the interpreter toevaluate an expression of the form\begin{verbatim}if (x > 0) log(x) else 0\end{verbatim}since [[if]] is a [[SPECIAL]] function. To make further improvementsthe compiler needs to be able to implement the [[if]] expression interms of conditional and unconditional branch instructions. It mightthen also be useful to implement [[>]] and [[log]] with specialvirtual machine instructions. To be able to do this, the compilerneeds to know that [[if]], [[>]], and [[log]] refer to the standardversions of these functions in the base package. While this is verylikely, it is not guaranteed.R is a very dynamic language. Functions defined in the base and otherpackages could be shadowed at runtime by definitions in loaded userpackages or by local definitions within a function. It is evenpossible for user code to redefine the functions in the base package,though this is discouraged by binding locking and would be poorprogramming practice. Finally, it is possible for functions calledprior to evaluating an expression like the one above to reach intotheir calling environment and add new definitions of [[log]] or [[if]]that wound then be used in evaluating this expression. Again this isnot common and generally not a good idea outside of a debuggingcontext.Ideally the compiler should completely preserve semantics of thelanguage implemented by the interpreter. While this is possible itwould significantly complicate the compiler and the compiled code, andcarry at least some runtime penalty. The approach taken here istherefore to permit the compiler to inline some functions when theyare not visibly shadowed in the compiled code. What the compiler ispermitted to do is determined by the setting of an optimization level.The details are desctibed in Section \ref{sec:inlining}.For the compiler to be able to decide whether is can inline a functionit needs to be able to determine whether there are any local variablethat might shadow a variable from a base package. This requires addingenvironment information to the compilation process.\subsection{Representing compilation environments}When compiling an expression the compiler needs to take into accountan evaluation environment, which would typically be a toplevelenvironment, along with local variable definitions discovered duringthe compilation process. The evaluation environment should not bemodified, so the local variables need to be considered in addition toones defined in the evaluation environment. If an expression\begin{verbatim}{ x <- 1; x + 2 }\end{verbatim}is compiled for evaluation in the global environment then existingdefinitions in the global environment as well as the new definitionfor [[x]] need to be taken into account. To address this thecompilation environment is a list of two components, an environmentand a list of character vectors. The environment consists of oneframe for each level of local variables followed by the top levelevaluation environment. The list of character vectors consist of oneelement for each frame for which local variables have been discoveredby the compiler. For efficiency the compilation environment structurealso includes a character vector [[ftype]] classifying each frame as alocal, namespace, or global frame.<<[[makeCenv]] function>>=## Create a new compiler environment## **** need to explain the structuremakeCenv <- function(env) {structure(list(extra = list(character(0)),env = env,ftypes = frameTypes(env)),class = "compiler_environment")}@ %def makeCenvWhen an expression is to be compiled in a particular environment afirst step is to identify any local variable definitions and add theseto the top level frame.<<[[addCenvVars]] function>>=## Add vars to the top compiler environment frameaddCenvVars <- function(cenv, vars) {cenv$extra[[1]] <- union(cenv$extra[[1]], vars)cenv}@ %def addCenVarsWhen compiling a function a new frame is added to the compilationenvironment. Typically a number of local variables are addedimmediately, so an optional [[vars]] argument is provided so thiscan be done without an additional call to [[addCenvVars]].<<[[addCenvFrame]] function>>=## Add a new frame to a compiler environmentaddCenvFrame <- function(cenv, vars) {cenv$extra <- c(list(character(0)), cenv$extra)cenv$env <- new.env(parent = cenv$env)cenv$ftypes <- c("local", cenv$ftypes)if (missing(vars))cenvelseaddCenvVars(cenv, vars)}@ %def addCenvFrameThe compilation environment is queried by calling [[findCenvVar]].%% **** change name to findCenvVarInfo or some such??If a binding for the specified variable is found then [[findCenvVar]]returns a list containing information about the binding. If nobinding is found then [[NULL]] is returned.<<[[findCenvVar]] function>>=## Find binding information for a variable (character or name).## If a binding is found, return a list containing components## ftype -- one of "local", "namespace", "global"## value -- current value if available## frame -- frame containing the binding (not useful for "local" variables)## index -- index of the frame (1 for top, 2, for next one, etc.)## Return NULL if no binding is found.## **** drop the index, maybe value, to reduce cost? (query as needed?)findCenvVar <- function(var, cenv) {if (typeof(var) == "symbol")var <- as.character(var)extra <- cenv$extraenv <- cenv$envframe <- NULL<<search [[extra]] entries and environment frames>><<search the remaining environment frames if necessary>><<create the [[findCenvVar]] result>>}@ %def findCenvVarThe initial search for a matching binding proceeds down each frame forwhich there is also an entry in [[extra]], searching the [[extra]]entry before the environment frame.<<search [[extra]] entries and environment frames>>=for (i in seq_along(cenv$extra)) {if (var %in% extra[[i]] || exists(var, env, inherits = FALSE)) {frame <- envbreak}elseenv <- parent.env(env)}@ %defIf [[frame]] is still [[NULL]] after the initial search then theremaining environment frames from the evaluation environment for whichthere are no corresponding entries in [[extra]] are searched.<<search the remaining environment frames if necessary>>=if (is.null(frame)) {empty <- emptyenv()while (! identical(env, empty)) {i <- i + 1if (exists(var, env, inherits = FALSE)) {frame <- envbreak}elseenv <- parent.env(env)}}@ %defIf a binding frame is found then the result consists of a listcontaining the frame, the frame type, the value if available, and theframe index. The value is not looked up for [[...]] variables. Apromise to compute the value is stored in an environment in theresult. This avoids computing the value in some cases where doing somay fail or produce unwanted side effects.<<create the [[findCenvVar]] result>>=if (! is.null(frame)) {if (exists(var, frame, inherits = FALSE) && var != "...") {value <- new.env(parent = emptyenv())delayedAssign("value", get(var, frame, inherits = FALSE),assign.env = value)}elsevalue <- NULLlist(frame = frame, ftype = cenv$ftypes[i], value = value, index = i)}elseNULL@ %defUseful functions for querying the environment associated with acompilation context are [[findVar]], [[findLocVar]], and[[findFunDef]]. The function [[findVar]] returns [[TRUE]] is a bindingfor the specified variable is visible and [[FALSE]] otherwise.<<[[findVar]] function>>=findVar <- function(var, cntxt) {cenv <- cntxt$envinfo <- findCenvVar(var, cenv)! is.null(info)}@ %def findVar[[findLocVar]] returns [[TRUE]] only if a local binding is found.<<[[findLocVar]] function>>=## test whether a local version of a variable might existfindLocVar <- function(var, cntxt) {cenv <- cntxt$envinfo <- findCenvVar(var, cenv)! is.null(info) && info$ftype == "local"}@ %def findLocVar[[findFunDef]] returns a function definition if one is available forthe specified name and [[NULL]] otherwise.<<[[findFunDef]] function>>=## **** should this check for local functions as well?findFunDef <- function(fun, cntxt) {cenv <- cntxt$envinfo <- findCenvVar(fun, cenv)if (! is.null(info$value) && is.function(info$value$value))info$value$valueelseNULL}@ %def findFunDef\subsection{Identifying possible local variables}For the compiler to be able to know that it can optimize a referenceto a particular global function or variable it needs to be able todetermine that that variable will not be shadowed by a localdefinition at runtime. R semantics do not allow certainidentification of local variables. If a function body consist of thetwo lines\begin{verbatim}if (x) y <- 1y\end{verbatim}then whether the variable [[y]] in the second line is local or globaldepends on the value of [[x]]. Lazy evaluation of arguments alsomeans what whether and when an assignment in a function argumentoccurs can be uncertain.The approach taken by the compiler is to conservatively identify allvariables that might be created within an expression, such as afunction body, and consider those to be potentially local variablesthat inhibit optimizations. This ignores runtime creation of newvariables, but as already mentioned that is generally not goodprogramming practice.Variables are created by the assignment operators [[<-]] and [[=]] andby [[for]] loops. In addition, calls to [[assign]] and[[delayedAssign]] with a literal character name argument areconsidered to create potential local variables if the environmentargument is missing, which means the assignment is in the currentenvironment.A simple approach for identifying all local variables created withinan expression is given by<<findlocals0>>=findLocals0 <- function(e) {if (typeof(e) == "language") {if (typeof(e[[1]]) %in% c("symbol", "character"))switch(as.character(e[[1]]),<<[[findLocals0]] switch clauses>>findLocalsList0(e[-1]))else findLocalsList0(e)}else character(0)}findLocalsList0 <- function(elist)unique(unlist(lapply(elist, findLocals0)))@ %def findLocals0 findLocalsList0For assignment expressions the assignment variable is added to anyvariables found in the value expression.<<[[findLocals0]] switch clauses>>="=" =,"<-" = unique(c(getAssignedVar(e),findLocalsList0(e[-1]))),@ %defThe assigned variable is determined by [[getAssignedVar]]:<<[[getAssignedVar]] function>>=getAssignedVar <- function(e) {v <- e[[2]]if (missing(v))stop(gettextf("bad assignment: %s", pasteExpr(e)),domain = NA)else if (typeof(v) %in% c("symbol", "character"))as.character(v)else {while (typeof(v) == "language") {if (length(v) < 2)stop(gettextf("bad assignment: %s", pasteExpr(e)),domain = NA)v <- v[[2]]if (missing(v))stop(gettextf("bad assignment: %s", pasteExpr(e)),domain = NA)}if (typeof(v) != "symbol")stop(gettextf("bad assignment: %s", pasteExpr(e)),domain = NA)as.character(v)}}@ %def getAssignedVarFor [[for]] loops the loop variable is added to any variables found inthe sequence and body expressions.<<[[findLocals0]] switch clauses>>="for" = unique(c(as.character(e[2]),findLocalsList0(e[-2]))),@ %defThe variable in [[assign]] and [[delayedAssign]] expressions isconsidered local if it is an explicit character string and there is noenvironment argument.<<[[findLocals0]] switch clauses>>="delayedAssign" =,"assign" = if (length(e) == 3 &&is.character(e[[2]]) &&length(e[[2]]) == 1)c(e[[2]], findLocals0(e[[3]], shadowed))else findLocalsList1(e[1], shadowed),@ %defVariables defined within local functions created by [[function]]expressions do not shadow globals within the containing expression andtherefore [[function]] expressions do not contribute any new localvariables. Similarly, [[local]] calls without an environment argumentcreate a new environment for evaluating their expression and do notadd new local variables. If an environment argument is present thenthis might be the current environment and so assignments in theexpression are considered to create possible local variables.Finally, [[~]], [[expression]], and [[quote]] do notevaluate their arguments and so do not contribute new local variables.<<[[findLocals0]] switch clauses>>="function" = character(0),"~" = character(0),"local" = if (length(e) == 2) character(0)else findLocalsList0(e[-1]),"expression" =,"quote" = character(0),@ %def findLocals0Other functions, for example [[Quote]] from the [[methods]] package,are also known to not evaluate their arguments but these do not oftencontain assignment expressions and so ignoring them only slightlyincreases the degree of conservatism in this approach.A problem with this simple implementation is that it assumes that allof the functions named in the [[switch]] correspond to the bindings inthe base package. This is reasonable for the ones that aresyntactically special, but not for [[expression]], [[local]] and[[quote]]. These might be shadowed by local definitions in asurrounding function. To allow for this we can add an optionalvariable [[shadowed]] for providing a character vector of names ofvariables with shadowing local definitions.<<[[findLocals1]] function>>=findLocals1 <- function(e, shadowed = character(0)) {if (typeof(e) == "language") {if (typeof(e[[1]]) %in% c("symbol", "character")) {v <- as.character(e[[1]])switch(v,<<[[findLocals1]] switch clauses>>findLocalsList1(e[-1], shadowed))}else findLocalsList1(e, shadowed)}else character(0)}@ %def findLocals1%% **** merge into single chunk??<<[[findLocalsList1]] function>>=findLocalsList1 <- function(elist, shadowed)unique(unlist(lapply(elist, findLocals1, shadowed)))@ %def findLocalsList1The handling of assignment operators, [[for]] loops, [[function]] and[[~]] expressions is analogous to the approach in [[findLocals0]].<<[[findLocals1]] switch clauses>>="=" =,"<-" = unique(c(getAssignedVar(e),findLocalsList1(e[-1], shadowed))),"for" = unique(c(as.character(e[2]),findLocalsList1(e[-2], shadowed))),"delayedAssign" =,"assign" = if (length(e) == 3 &&is.character(e[[2]]) &&length(e[[2]]) == 1)c(e[[2]], findLocals1(e[[3]], shadowed))else findLocalsList1(e[1], shadowed),"function" = character(0),"~" = character(0),@ %defThe rules for ignoring assignments in [[local]], [[expression]], and[[quote]] calls are only applied if there are no shadowingdefinitions.<<[[findLocals1]] switch clauses>>="local" = if (! v %in% shadowed && length(e) == 2)character(0)else findLocalsList1(e[-1], shadowed),"expression" =,"quote" = if (! v %in% shadowed)character(0)else findLocalsList1(e[-1], shadowed),@ %defThe assignment functions could also be shadowed, but this is not verycommon, and assuming that they are not errs in the conservativedirection.This approach can handle the case where [[quote]] or one of the othernon-syntactic functions is shadowed by an outer definition but does nothandle assignments that occur in the expression itself. For example,in\begin{verbatim}function (f, x, y) {local <- flocal(x <- y)x}\end{verbatim}the reference to [[x]] in the third line has to be consideredpotentially local. To deal with this multiple passes are needed. Thefirst pass assumes that [[expression]], [[local]] or [[quote]] mightbe shadowed by local assignments. If no assignments to some of themare visible, then a second pass can be used in which they are assumednot to be shadowed. This can be iterated to convergence. It is alsouseful to check before returning whether any of the syntacticallyspecial variables has been assigned to. If so, so a warning isissued.%% **** allow overriding of the warning function by cntxt?%% **** look into speeding up findLocalsList<<[[findLocalsList]] function>>=findLocalsList <- function(elist, cntxt) {initialShadowedFuns <- c("expression", "local", "quote")shadowed <- Filter(function(n) ! isBaseVar(n, cntxt), initialShadowedFuns)specialSyntaxFuns <- c("~", "<-", "=", "for", "function")sf <- initialShadowedFunsnsf <- length(sf)repeat {vals <- findLocalsList1(elist, sf)redefined <- sf %in% valslast.nsf <- nsfsf <- unique(c(shadowed, sf[redefined]))nsf <- length(sf)## **** need to fix the termination condition used in codetools!!!if (last.nsf == nsf) {rdsf <- vals %in% specialSyntaxFunsif (any(rdsf)) {msg <- ngettext(sum(rdsf),"local assignment to syntactic function: ","local assignments to syntactic functions: ")warning(msg, paste(vals[rdsf], collapse = ", "),domain = NA)}return(vals)}}}@ %def findLocalsList<<[[findLocals]] function>>=findLocals <- function(e, cntxt)findLocalsList(list(e), cntxt)@ %def findLocalsStandard definitions for all functions in [[initialShadowedFuns]] arein the base package and [[isBaseVar]] checks the compilationenvironment to see whether the specified variable's definition comesfrom that package either via a namespace or the global environment.<<[[isBaseVar]] function>>=isBaseVar <- function(var, cntxt) {info <- getInlineInfo(var, cntxt)(! is.null(info) &&(identical(info$frame, .BaseNamespaceEnv) ||identical(info$frame, baseenv())))}@ %def isBaseVarThe use of [[getInlineInfo]], defined in Section \ref{sec:inlining}, meansthat the setting of the [[optimize]] compiler option will influence whethera variable should be considered to be from the base package or not.It might also be useful to warn about assignments to other functions.When a [[function]] expression is compiled, its body and defaultarguments need to be compiled using a compilation environment thatcontains a new frame for the function that contains variables for thearguments and any assignments in the body and the default expressions.[[funEnv]] creates such an environment.<<[[funEnv]] function>>=## augment compiler environment with function args and localsfunEnv <- function(forms, body, cntxt) {cntxt$env <- addCenvFrame(cntxt$env, names(forms))locals <- findLocalsList(c(forms, body), cntxt)addCenvVars(cntxt$env, locals)}@ %def funEnv\section{The inlining mechanism}\label{sec:inlining}To allow for inline coding of calls to some functions the [[cmpCall]]function calls the [[tryInline]] function. The [[tryInline]] functionwill either generate code for the call and return [[TRUE]], or it willdecline to do so and return [[FALSE]], in which case the standard codegeneration process for a function call is used.The function [[tryInline]] calls [[getInlineInfo]] to determinewhether inlining is permissible given the current environment andoptimization settings. There are four possible optimization levels:\begin{description}\item[Level 0:] No inlining.\item[Level 1:] Functions in the base packages found through anamespace that are not shadowed by function arguments or visiblelocal assignments may be inlined.\item[Level 2:] In addition to the inlining permitted by Level 1,functions that are syntactically special or are considered corelanguage functions and are found via the global environment atcompile time may be inlined.\item[Level 3:] Any function in the base packages found via the globalenvironment may be inlined.%% **** should there be an explicit list of functions where inlining%% **** is OK here??\end{description}The syntactically special and core language functions are<<[[languageFuns]] definition>>=languageFuns <- c("^", "~", "<", "<<-", "<=", "<-", "=", "==", ">", ">=","|", "||", "-", ":", "!", "!=", "/", "(", "[", "[<-", "[[","[[<-", "{", "@", "$", "$<-", "*", "&", "&&", "%/%", "%*%","%%", "+","::", ":::", "@<-","break", "for", "function", "if", "next", "repeat", "while","local", "return", "switch")@ %def languageFuns%% **** local, return, and switch are dubious here%% **** if we allow them, should we also allow a few others, like .Internal?The default optimization level is Level 2. Future versions of thecompiler may allow some functions to be explicitly excluded frominlining and may provide a means for allowing user-defined functionsto be declared eligible for inlining.If inlining is permissible then the result returned by[[getInlineInfo]] contains the packages associated with the specifiedvariable in the current environment. The variable name and package arethen looked up in a data base of handlers. If a handler is found thenthe handler is called. The handler can either generate code andreturn [[TRUE]] or decline to and return [[FALSE]]. If inlining isnot possible then [[getInlineInfo]] returns [[NULL]] and [[tryInline]]returns [[FALSE]].%% **** think about adding GETNSFUN to use when inlining is OK<<[[tryInline]] function>>=tryInline <- function(e, cb, cntxt) {name <- as.character(e[[1]])info <- getInlineInfo(name, cntxt)if (is.null(info))FALSEelse {h <- getInlineHandler(name, info$package)if (! is.null(h))h(e, cb, cntxt)else FALSE}}@ %def tryInlineThe function [[getInlineInfo]] implements the optimization rulesdescribed at the beginning of this section.<<[[getInlineInfo]] function>>=getInlineInfo <- function(name, cntxt) {optimize <- cntxt$optimizeif (optimize > 0) {info <- findCenvVar(name, cntxt$env)if (is.null(info))NULLelse {ftype <- info$ftypeframe <- info$frameif (ftype == "namespace") {<<fixup for a namespace import frame>>info$package <- nsName(findHomeNS(name, frame))info}else if (ftype == "global" &&(optimize >= 3 ||(optimize >= 2 && name %in% languageFuns))) {info$package <- packFrameName(frame)info}else NULL}}else NULL}@ %def getInlineInfoThe code for finding the home namespace from a namespace import frameis needed here to deal with the fact that a namespace may not beregistered when this function is called, so the mechanism used in[[findHomeNS]] to locate the namespace to which an import framebelongs may not work.<<fixup for a namespace import frame>>=if (! isNamespace(frame)) {## should be the import frame of the current topenvtop <- topenv(cntxt$env$env)if (! isNamespace(top) ||! identical(frame, parent.env(top)))cntxt$stop(gettext("bad namespace import frame"))frame <- top}@For this version of the compiler the inline handler data base ismanaged as an environment in which handlers are entered and looked upby name. For now it is assumed that a name can only appear associatedwith one package and an error is signaled if an attempt is made toredefine a handler for a given name for a different package than anexisting definition. This can easily be changed if it should prove toorestrictive.%% **** note on haveInlineHandler%% **** allow same name in multiple packages?<<inline handler implementation>>=inlineHandlers <- new.env(hash = TRUE, parent = emptyenv())setInlineHandler <- function(name, h, package = "base") {if (exists(name, inlineHandlers, inherits = FALSE)) {entry <- get(name, inlineHandlers)if (entry$package != package) {fmt <- "handler for '%s' is already defined for another package"stop(gettextf(fmt, name), domain = NA)}}entry <- list(handler = h, package = package)assign(name, entry, inlineHandlers)}getInlineHandler <- function(name, package = "base") {if (exists(name, inlineHandlers, inherits = FALSE)) {hinfo <- get(name, inlineHandlers)if (hinfo$package == package)hinfo$handlerelse NULL}else NULL}haveInlineHandler <- function(name, package = "base") {if (exists(name, inlineHandlers, inherits = FALSE)) {hinfo <- get(name, inlineHandlers)package == hinfo$package}else FALSE}@ %def inlineHandlers getInlineHandler setInlineHandler haveInlineHandler\section{Default inlining rules for primitives}This section defines generic handlers for [[BUILTIN]] and [[SPECIAL]]functions. These are installed programmatically for all [[BUILTIN]]and [[SPECIAL]] functions. The following sections present morespecialized handlers for a range of functions that are installed inplace of the default ones.<<install default inlining handlers>>=local({<<install default [[BUILTIN]] handlers>><<install default [[SPECIAL]] handlers>>})@The handler installations are wrapped in a [[local]] call to reduceenvironment pollution.\subsection{[[BUILTIN]] functions}Calls to functions known at compile time to be of type [[BUILTIN]] canbe handled more efficiently. The interpreter evaluates all argumentsfor [[BUILTIN]] functions before calling the function, so the compilercan evaluate the arguments in line without the need for creatingpromises.A generic handler for inlining a call to a [[BUILIN]] function isprovided by [[cmpBuiltin]]. For now, the handler returns [[FALSE]] ifthe call contains missing arguments, which are currently not allowedin [[BUILTIN]] functions, or [[...]] arguments. The handling of[[...]] arguments should be improved.%% **** look into improving handling ... arguments to BUILTINs?For [[BUILTIN]] functions the function to call is pushed on the stackwith the [[GETBUILTIN]] instruction. The [[internal]] argument allows[[cmpBuiltin]] to be used with [[.Internal]] functions of type[[BUILTIN]] as well; this is used in the handler for [[.Internal]]defined in Section \ref{subsec:.Internal}.<<[[cmpBuiltin]] function>>=cmpBuiltin <- function(e, cb, cntxt, internal = FALSE) {fun <- e[[1]]args <- e[-1]names <- names(args)if (dots.or.missing(args))FALSEelse {ci <- cb$putconst(fun)if (internal)cb$putcode(GETINTLBUILTIN.OP, ci)elsecb$putcode(GETBUILTIN.OP, ci)cmpBuiltinArgs(args, names, cb, cntxt)ci <- cb$putconst(e)cb$putcode(CALLBUILTIN.OP, ci)if (cntxt$tailcall) cb$putcode(RETURN.OP)TRUE}}@ %def cmpBuiltinArgument evaluation code is generated by [[cmpBuiltinArgs]]. In thecontext of [[BUILTIN]] functions missing arguments are currently notallowed. But to allow [[cmpBuiltinArgs]] to be used in other contextsmissing arguments are supported if the optional argument [[missingOK]]is [[TRUE]].%% **** should this warn/stop if there are missings and missingOK is FALSE??%% **** can this be adjusted so error messages match the interpreter?%% **** for f <- function(x, y) x + y compare errors for f(1,) and cmpfun(f)(1,)%% **** test code for constant folding is needed (sym and non-sym)<<[[cmpBuiltinArgs]] function>>=cmpBuiltinArgs <- function(args, names, cb, cntxt, missingOK = FALSE) {ncntxt <- make.argContext(cntxt)for (i in seq_along(args)) {a <- args[[i]]n <- names[[i]]<<compile missing [[BUILTIN]] argument>>## **** handle ... here ??<<signal an error for promise or bytecode argument>><<compile a general [[BUILTIN]] argument>>}}@ %def cmpBuiltinArgsMissing argument code is generated by<<compile missing [[BUILTIN]] argument>>=if (missing(a)) {if (missingOK) {cb$putcode(DOMISSING.OP)cmpTag(n, cb)}elsecntxt$stop(gettext("missing arguments are not allowed"), cntxt)}@The error case should not be reached as [[cmpBuiltinArgs]] should notbe called with missing arguments unless [[missingOK]] is [[TRUE]].The code for general arguments handles symbols separately to allow forthe case when missing values are acceptable. Constant folding istried first since the constant folding code in [[cmp]] is not reachedin this case. Constant folding is needed here since it doesn't gothrough [[cmp]].<<compile a general [[BUILTIN]] argument>>=else {if (is.symbol(a)) {ca <- constantFold(a, cntxt)if (is.null(ca)) {cmpSym(a, cb, ncntxt, missingOK)cb$putcode(PUSHARG.OP)}elsecmpConstArg(ca$value, cb, cntxt)}else if (typeof(a) == "language") {cmp(a, cb, ncntxt)cb$putcode(PUSHARG.OP)}elsecmpConstArg(a, cb, cntxt)cmpTag(n, cb)}@ %defHandling the constant case separately is not really necessary butmakes the code a bit cleaner.Default handlers for all [[BUILTIN]] functions in the [[base]] packageare installed programmatically by<<install default [[BUILTIN]] handlers>>=basevars <- ls('package:base', all = TRUE)types <- sapply(basevars, function(n) typeof(get(n)))for (s in basevars[types == "special"])if (! haveInlineHandler(s, "base"))setInlineHandler(s, cmpSpecial)@ %def\subsection{[[SPECIAL]] functions}Calls to functions known to be of type [[SPECIAL]] can also becompiled somewhat more efficiently by the [[cmpSpecial]] function:<<[[cmpSpecial]] function>>=cmpSpecial <- function(e, cb, cntxt) {fun <- e[[1]]if (typeof(fun) == "character")fun <- as.name(fun)ci <- cb$putconst(e)cb$putcode(CALLSPECIAL.OP, ci)if (cntxt$tailcall)cb$putcode(RETURN.OP)TRUE}@ %def cmpSpecialThis handler is installed for all [[SPECIAL]] functions in the basepackage with<<install default [[SPECIAL]] handlers>>=for (b in basevars[types == "builtin"])if (! haveInlineHandler(b, "base"))setInlineHandler(b, cmpBuiltin)@ %def\section{Some simple inlining handlers}This section presents inlining handlers for a number of core primitivefunctions. With these additions the compiler will begin to show someperformance improvements.\subsection{The left brace sequencing function}The inlining handler for [[{]] needs to consider that a pair of braces[[{]] and [[}]] can surround zero, one, or more expressions. A setof empty braces is equivalent to the constant [[NULL]]. If there ismore than one expression, then all the values of all expressions otherthan the last are ignored. These expressions are compiled in ano-value context (currently equivalent to a non-tail-call context),and then code is generated to pop their values off the stack. Thefinal expression is then compiled according to the context in whichthe braces expression occurs.<<inlining handler for left brace function>>=setInlineHandler("{", function(e, cb, cntxt) {n <- length(e)if (n == 1)cmp(NULL, cb, cntxt)else {if (n > 2) {ncntxt <- make.noValueContext(cntxt)for (i in 2 : (n - 1)) {cmp(e[[i]], cb, ncntxt)cb$putcode(POP.OP)}}cmp(e[[n]], cb, cntxt)}TRUE})@ %def\subsection{The closure constructor function}Compiling of [[function]] expressions is somewhat similar to compilingpromises for function arguments. The body of a function is compiledinto a separate byte code object and stored in the constant pooltogether with the formals. Then code is emitted for creating aclosure from the formals, compiled body, and the current environment.For now, only the body of functions is compiled, not thedefault argument expressions. This should be changed in futureversions of the compiler.<<inlining handler for [[function]]>>=setInlineHandler("function", function(e, cb, cntxt) {forms <- e[[2]]body <- e[[3]]ncntxt <- make.functionContext(cntxt, forms, body)cbody <- genCode(body, ncntxt)ci <- cb$putconst(list(forms, cbody))cb$putcode(MAKECLOSURE.OP, ci)if (cntxt$tailcall) cb$putcode(RETURN.OP)TRUE})@ %def\subsection{The left parenthesis function}In R an expression of the form [[(expr)]] is interpreted as a call tothe function [[(]] with the argument [[expr]]. Parentheses are usedto guide the parser, and for the most part [[(expr)]] is equivalent to[[expr]]. There are two exceptions:\begin{itemize}\item Since [[(]] is a function an expression of the form [[(...)]] islegal whereas just [[...]] may not be, depending on the context. Aruntime error will occur unless the [[...]] argument expands toexactly one non-missing argument.\item In tail position a call to [[(]] sets the visible flag to[[TRUE]]. So at top level for example the result of an assignmentexpression [[x <- 1]] would not be printed, but the result of [[(x<- 1]] would be printed. It is not clear that this feature reallyneeds to be preserved within functions --- it could be made afeature of the read-eval-print loop --- but for now it is a featureof the interpreter that the compiler should preserve.\end{itemize}The inlining handler for [[(]] calls handles a [[...]] argument caseor a case with fewer or more than one argument as a generic[[BUILTIN]] call. If the expression is in tail position then theargument is compiled in a non-tail-call context, a [[VISIBLE]]instruction is emitted to set the visible flag to [[TRUE]], and a[[RETURN]] instruction is emitted. If the expression is in non-tailposition, then code for the argument is generated in the current context.<<inlining handler for [[(]]>>=setInlineHandler("(", function(e, cb, cntxt) {if (any.dots(e))cmpBuiltin(e, cb, cntxt) ## puntelse if (length(e) != 2) {notifyWrongArgCount("(", cntxt)cmpBuiltin(e, cb, cntxt) ## punt}else if (cntxt$tailcall) {ncntxt <- make.nonTailCallContext(cntxt)cmp(e[[2]], cb, ncntxt)cb$putcode(VISIBLE.OP)cb$putcode(RETURN.OP)TRUE}else {cmp(e[[2]], cb, cntxt)TRUE}})@ %def\subsection{The [[.Internal]] function}\label{subsec:.Internal}One frequently used [[SPECIAL]] function is [[.Internal]]. When the[[.Internal]] function called is of type [[BUILTIN]] it is useful tocompile the call as for a [[BUILTIN]] function. For [[.Internal]]functions of type [[SPECIAL]] there is less of an advantage, and sothe [[.Internal]] expression is compiled with [[cmpSpecial]]. It maybe useful to use introduce a [[GETINTLSPECIAL]] instruction and handlethese analogously to [[.Internal]] functions of type [[BUILTIN]].%% **** look into adding GETINTLSPECIAL??<<inlining handler for [[.Internal]]>>=setInlineHandler(".Internal", function(e, cb, cntxt) {ee <- e[[2]]sym <- ee[[1]]if (.Internal(is.builtin.internal(sym)))cmpBuiltin(ee, cb, cntxt, internal = TRUE)elsecmpSpecial(e, cb, cntxt)})@ %def\subsection{The [[local]] function}While [[local]] is currently implemented as a closure, because of itsimportance relative to local variable determination it is a good ideato inline it as well. The current semantics are such that theinterpreter treats\begin{verbatim}local(expr)\end{verbatim}essentially the same as\begin{verbatim}(function() expr)()\end{verbatim}There may be some minor differences related to what the [[sys.xyz]]functions return but these do not seem to be important. So thecompiler handles one argument [[local]] calls by making thisconversion and compiling the result.%% **** add to language manual?<<inlining handler for [[local]] function>>=setInlineHandler("local", function(e, cb, cntxt) {if (length(e) == 2) {ee <- as.call(list(as.call(list(as.name("function"), NULL, e[[2]]))))cmp(ee, cb, cntxt)TRUE}else FALSE})@ %def\subsection{The [[return]] function}\label{subsec:return}A call to [[return]] causes a return from the associated functioncall, as determined by the lexical context in which the [[return]]expression is defined. If the [[return]] is captured in a closure andis executed within a callee then this requires a [[longjmp]]. A[[longjmp]] is also needed if the [[return]] call occurs within a loopthat is compiled to a separate code object to support a [[setjmp]] for[[break]] or [[next]] calls. The [[RETURNJMP]] instruction isprovided for this purpose. In all other cases an ordinary [[RETURN]]instruction can be used.%% **** if function body code was tagged as such then changing from%% **** RETURNJMP to RETURN could be done by post-processing the%% **** bytecode or by putcode[[return]] calls with [[...]], which may be legal if [[...]] containsonly one argument, or missing arguments or more than one argument,which will produce runtime errors, are compiled as generic [[SPECIAL]]calls.<<inlining handler for [[return]] function>>=setInlineHandler("return", function(e, cb, cntxt) {if (dots.or.missing(e) || length(e) > 2)cmpSpecial(e, cb, cntxt) ## **** punt for nowelse {if (length(e) == 1)val <- NULLelseval <- e[[2]]ncntxt <- make.nonTailCallContext(cntxt)cmp(val, cb, ncntxt)if (cntxt$needRETURNJMP)cb$putcode(RETURNJMP.OP)elsecb$putcode(RETURN.OP)}TRUE})@\section{Branching and labels}The code generated so far is straight line code without conditional orunconditional branches. To implement conditional evaluationconstructs and loops we need to add conditional and unconditionalbranching instructions. These make use of the labels mechanismprovided by the code buffer.\subsection{Inlining handler for [[if]] expressions}Using the labels mechanism we can implement an inlining handler for[[if]] expressions. The first step extracts the components of theexpression. An [[if]] expression with no [[else]] clause willinvisibly return [[NULL]] if the test is [[FALSE]], but the visibleflag setting only matters if the [[if]] expression is in tailposition. So the case of no [[else]] clause will be handled slightlydifferently in tail and non-tail contexts.%% **** In no value contexts it would be good to avoid pushing and%% **** immediately popping constants. Alternatively a peephole optimizer%% **** could clean these up.%% **** Should there be error checking for either two or three arguments here??<<[[if]] inline handler body>>=test <- e[[2]]then.expr <- e[[3]]if (length(e) == 4) {have.else.expr <- TRUEelse.expr <- e[[4]]}else have.else.expr <- FALSE@ %defTo deal with use of [[if (FALSE) ...]] for commenting out code and of[[if (is.R()) ... else ...]] for handling both R and Splus code it isuseful to attempt to constant-fold the test. If this succeeds andproduces either [[TRUE]] or [[FALSE]] then only the appropriate branchis compiled and the handler returns [[TRUE]].<<[[if]] inline handler body>>=ct <- constantFold(test, cntxt)if (! is.null(ct) && is.logical(ct$value) && length(ct$value) == 1&& ! is.na(ct$value)) {if (ct$value)cmp(then.expr, cb, cntxt)else if (have.else.expr)cmp(else.expr, cb, cntxt)else if (cntxt$tailcall) {cb$putcode(LDNULL.OP)cb$putcode(INVISIBLE.OP)cb$putcode(RETURN.OP)}else cb$putcode(LDNULL.OP)return(TRUE)}@Next, the test code is compiled, a label for the start of code for the[[else]] clause is generated, and a conditional branch instructionthat branches to the [[else]] label if the test fails is emitted.This is followed by code for the consequent (test is [[TRUE]])expression. The [[BRIFNOT]] takes two operand, the constant poolindex for the call and the label to branch to if the value on thestack is [[FALSE]]. The call is used if an error needs to be signaledfor an improper test result on the stack.<<[[if]] inline handler body>>=ncntxt <- make.nonTailCallContext(cntxt)cmp(test, cb, ncntxt)callidx <- cb$putconst(e)else.label <- cb$makelabel()cb$putcode(BRIFNOT.OP, callidx, else.label)cmp(then.expr, cb, cntxt)@The code for the alternative [[else]] expression will be placed afterthe code for the consequent expression. If the [[if]] expressionappears in tail position then the code for the consequent will end witha [[RETURN]] instruction and there is no need to jump over thefollowing instructions for the [[else]] expression. All that is neededis to record the value of the label for the [[else]] clause and toemit the code for the [[else]] clause. If no [[else]] clause wasprovided then that code arranges for the value [[NULL]] to be returnedinvisibly.<<[[if]] inline handler body>>=if (cntxt$tailcall) {cb$putlabel(else.label)if (have.else.expr)cmp(else.expr, cb, cntxt)else {cb$putcode(LDNULL.OP)cb$putcode(INVISIBLE.OP)cb$putcode(RETURN.OP)}}@ %defOn the other hand, if the [[if]] expression is not in tail positionthen a label for the next instruction after the [[else]] expressioncode is needed, and the consequent expression code needs to end with a[[GOTO]] instruction to that label. If the expression does notinclude an [[else]] clause then the alternative code just places[[NULL]] on the stack.<<[[if]] inline handler body>>=else {end.label <- cb$makelabel()cb$putcode(GOTO.OP, end.label)cb$putlabel(else.label)if (have.else.expr)cmp(else.expr, cb, cntxt)elsecb$putcode(LDNULL.OP)cb$putlabel(end.label)}@ %defThe resulting handler definition is<<inlining handler for [[if]]>>=setInlineHandler("if", function(e, cb, cntxt) {## **** test for missing, ...<<[[if]] inline handler body>>TRUE})@ %def%% **** need some assembly code examples??\subsection{Inlining handlers for [[&&]] and [[||]] expressions}In many languages it is possible to convert the expression [[a && b]]to an equivalent [[if]] expression of the form\begin{verbatim}if (a) { if (b) TRUE else FALSE }\end{verbatim}Similarly, in these languages the expression [[a || b]] is equivalentto\begin{verbatim}if (a) TRUE else if (b) TRUE else FALSE\end{verbatim}Compilation of these expressions is thus reduced to compiling [[if]]expressions.Unfortunately, because of the possibility of [[NA]] values, theseequivalencies do not hold in R. In R, [[NA || TRUE]] should evaluateto [[TRUE]] and [[NA && FALSE]] to [[FALSE]]. This is handled byintroducing special instructions [[AND1ST]] and [[AND2ND]] for [[&&]]expressions and [[OR1ST]] and [[OR2ND]] for [[||]].The code generator for [[&&]] expressions generates code to evaluatethe first argument and then emits an [[AND1ST]] instruction. The[[AND1ST]] instruction has one operand, the label for the instructionfollowing code for the second argument. If the value on the stackproduced by the first argument is [[FALSE]] then [[AND1ST]] jumps tothe label and skips evaluation of the second argument; the value ofthe expression is [[FALSE]]. The code for the second argument isgenerated next, followed by an [[AND2ND]] instruction. This removesthe values of the two arguments to [[&&]] from the stack and pushesthe value of the expression onto the stack. A [[RETURN]] instructionis generated if the [[&&]] expression was in tail position.%% **** check over all uses of argContext vs nonTailCallContext%% **** The first argument can use nonTailCallCOntext because nothing%% **** is on the stack yet. The second one has to use argContext.%% **** This wouldn't be an issue if break/next could reset the stack%% **** before the jump.<<inlining handler for [[&&]]>>=setInlineHandler("&&", function(e, cb, cntxt) {## **** arity check??ncntxt <- make.argContext(cntxt)callidx <- cb$putconst(e)label <- cb$makelabel()cmp(e[[2]], cb, ncntxt)cb$putcode(AND1ST.OP, callidx, label)cmp(e[[3]], cb, ncntxt)cb$putcode(AND2ND.OP, callidx)cb$putlabel(label)if (cntxt$tailcall)cb$putcode(RETURN.OP)TRUE})@ %defThe code generator for [[||]] expressions is analogous.<<inlining handler for [[||]]>>=setInlineHandler("||", function(e, cb, cntxt) {## **** arity check??ncntxt <- make.argContext(cntxt)callidx <- cb$putconst(e)label <- cb$makelabel()cmp(e[[2]], cb, ncntxt)cb$putcode(OR1ST.OP, callidx, label)cmp(e[[3]], cb, ncntxt)cb$putcode(OR2ND.OP, callidx)cb$putlabel(label)if (cntxt$tailcall)cb$putcode(RETURN.OP)TRUE})@ %def\section{Loops}\label{sec:loops}In principle code for [[repeat]] and [[while]] loops can be generatedusing just [[GOTO]] and [[BRIFNOT]] instructions. [[for]] loopsrequire a little more to manage the loop variable and termination. Acomplication arises due to the need to support [[break]] and [[next]]calls in the context of lazy evaluation of arguments: if a [[break]]or [[next]] expression appears in a function argument that is compiledas a closure, then the expression may be evaluated deep inside aseries of nested function calls and require a non-local jump. Asimilar issue arises for calls to the [[return]] function as describedin Section \ref{subsec:return}.To support these non-local jumps the interpreter sets up a [[setjmp]]context for each loop, and [[break]] and [[next]] use [[longjmp]] totransfer control. In general, compiled loops need to use a similarapproach. For now, this is implemented by the [[STARTLOOPCNTXT]] and[[ENDLOOPCNTXT]] instructions. The [[STARTLOOPCNTXT]] instructionstakes one argument, the constant pool index for a code objectimplementing the loop body. The loop body should end with a call to[[ENDLOOPCNTXT]].At least with some assumptions it is often possible to implement[[break]] and [[next]] calls as simple [[GOTO]]s. If all [[break]]and [[next]] calls in a loop can be implemented using [[GOTO]]s thenthe loop context is not necessary. The mechanism to enable thesimpler code generation is presented in Section\ref{subsec:skipcntxt}.The current engine implementation executes one [[setjmp]] per[[STARTLOOPCNTXT]] and uses nested calls to [[bceval]] to run thecode. Eventually we should be able to reduce the need for nested[[bceval]] calls and to arrange that [[setjmp]] buffers be reused formultiple purposes.\subsection{[[repeat]] loops}The simplest loop in R is the [[repeat]] loop. The code generator isdefined as<<inlining handler for [[repeat]] loops>>=setInlineHandler("repeat", function(e, cb, cntxt) {body <- e[[2]]<<generate context and body code for [[repeat]] loop>><<generate [[repeat]] and [[while]] loop wrap-up code>>TRUE})@ %defIf a loop context is not needed then the code for the loop body isjust written to the original code buffer. The [[else]] clause in thecode chunk below generates the code for the general case. [[genCode]]is used to produce a new code object containing code for the loop bodyfollowed by the [[ENDLOOPCNTXT]] instruction, The code object is theninstalled in the context pool. The [[STARTLOOPCNTXT]] instruction isemitted to the main code buffer. When a separate code object iswritten the need for using [[RETURNJMP]] for [[return]] calls isindicated by setting the [[needRETURNJMP]] flag in the compilercontext to [[TRUE]].<<generate context and body code for [[repeat]] loop>>=if (checkSkipLoopCntxt(body, cntxt))cmpRepeatBody(body, cb, cntxt)else {cntxt$needRETURNJMP <- TRUE ## **** do this a better waycode <- genCode(body, cntxt,function(cb, cntxt) {cmpRepeatBody(body, cb, cntxt)cb$putcode(ENDLOOPCNTXT.OP)})bi <- cb$putconst(code)cb$putcode(STARTLOOPCNTXT.OP, bi)}@ %defThe loop body uses two labels. [[loop.label]] marks the top of theloop and is the target of the [[GOTO]] instruction at the end of thebody. This label is also used by [[next]] expressions that do notrequire [[longjmp]]s. The [[end.loop]] label is placed after the[[GOTO]] instruction and is used by [[break]] expressions that do notrequire [[longjmp]]s. The body is compiled in a context that makesthese labels available, and the value left on the stack is removed bya [[POP]] instruction. The [[POP]] instruction is followed by a[[GOTO]] instruction that returns to the top of the loop.<<[[cmpRepeatBody]] function>>=cmpRepeatBody <- function(body, cb, cntxt) {loop.label <- cb$makelabel()end.label <- cb$makelabel()cb$putlabel(loop.label)lcntxt <- make.loopContext(cntxt, loop.label, end.label)cmp(body, cb, lcntxt)cb$putcode(POP.OP)cb$putcode(GOTO.OP, loop.label)cb$putlabel(end.label)}@ %def cmpRepeatBodyThe wrap-up code for the loop places the [[NULL]] value of the loopexpression on the stack and emits [[INVISIBLE]] and [[RETURN]]instructions to return the value if the loop appears in tail position.<<generate [[repeat]] and [[while]] loop wrap-up code>>=cb$putcode(LDNULL.OP)if (cntxt$tailcall) {cb$putcode(INVISIBLE.OP)cb$putcode(RETURN.OP)}@ %defThe [[break]] and [[next]] code generators emit [[GOTO]] instructionsif the loop information is available and the [[gotoOK]] compilercontext flag is [[TRUE]]. A warning is issued if no loop is visiblein the compilation context.<<inlining handlers for [[next]] and [[break]]>>=setInlineHandler("break", function(e, cb, cntxt) {if (is.null(cntxt$loop)) {notifyWrongBreakNext("break", cntxt)cmpSpecial(e, cb, cntxt)}else if (cntxt$loop$gotoOK) {cb$putcode(GOTO.OP, cntxt$loop$end)TRUE}else cmpSpecial(e, cb, cntxt)})setInlineHandler("next", function(e, cb, cntxt) {if (is.null(cntxt$loop)) {notifyWrongBreakNext("next", cntxt)cmpSpecial(e, cb, cntxt)}else if (cntxt$loop$gotoOK) {cb$putcode(GOTO.OP, cntxt$loop$loop)TRUE}else cmpSpecial(e, cb, cntxt)})@ %def\subsection{[[while]] loops}%% could just compile repeat{ if (condition) body else break } ??The structure for the [[while]] loop code generator is similar to thestructure of the [[repeat]] code generator:<<inlining handler for [[while]] loops>>=setInlineHandler("while", function(e, cb, cntxt) {cond <- e[[2]]body <- e[[3]]<<generate context and body code for [[while]] loop>><<generate [[repeat]] and [[while]] loop wrap-up code>>TRUE})@ %defThe context and body generation chunk is similar as well. Theexpression stored in the code object isn't quite right as what iscompiled includes both the test and the body, but this code objectshould not be externally visible.<<generate context and body code for [[while]] loop>>=if (checkSkipLoopCntxt(cond, cntxt) && checkSkipLoopCntxt(body, cntxt))cmpWhileBody(e, cond, body, cb, cntxt)else {cntxt$needRETURNJMP <- TRUE ## **** do this a better waycode <- genCode(body, cntxt, ## **** expr isn't quite rightfunction(cb, cntxt) {cmpWhileBody(e, cond, body, cb, cntxt)cb$putcode(ENDLOOPCNTXT.OP)})bi <- cb$putconst(code)cb$putcode(STARTLOOPCNTXT.OP, bi)}@ %defAgain two labels are used, one at the top of the loop and one at theend. The [[loop.label]] is followed by code for the test. Next is a[[BRIFNOT]] instruction that jumps to the end of the loop if the valueleft on the stack by the test is [[FALSE]]. This is followed by thecode for the body, a [[POP]] instruction, and a [[GOTO]] instructionthat jumps to the top of the loop. Finally, the [[end.label]] isrecorded.<<[[cmpWhileBody]] function>>=cmpWhileBody <- function(call, cond, body, cb, cntxt) {loop.label <- cb$makelabel()end.label <- cb$makelabel()cb$putlabel(loop.label)lcntxt <- make.loopContext(cntxt, loop.label, end.label)cmp(cond, cb, lcntxt)callidx <- cb$putconst(call)cb$putcode(BRIFNOT.OP, callidx, end.label)cmp(body, cb, lcntxt)cb$putcode(POP.OP)cb$putcode(GOTO.OP, loop.label)cb$putlabel(end.label)}@ cmpWhileBody\subsection{[[for]] loops}%% could compile repeat { if (stepfor) body else break } and peephole a bit ??Code generation for [[for]] loops is a little more complex because ofthe need to manage the loop variable value and stepping through thesequence. Code for [[for]] loops uses three additional instructions.[[STARTFOR]] takes the constant pool index of the call, the constantpool index of the loop variable symbol, and the label of the startinstruction as operands. It finds the sequence to iterate over on thestack and places information for accessing the loop variable bindingand stepping the sequence on the stack before jumping to the label.The call is used if an error for an improper for loop sequence needsto be signaled. The [[STEPFOR]] instruction takes a label for the topof the loop as its operand. If there are more elements in thesequence then [[STEPFOR]] advances the position within the sequence,sets the loop variable, and jumps to the top of the loop. Otherwiseit drops through to the next instruction. Finally [[ENDFOR]] cleansup the loop information stored on the stack by [[STARTFOR]] and leavesthe [[NULL]] loop value on the stack.The inlining handler for a [[for]] loop starts out by checking theloop variable and issuing a warning if it is not a symbol. The codegenerator then declines to inline the loop expression. This means itis compiled as a generic function call and will signal an error atruntime. An alternative would be do generate code to signal the erroras is done with improper use of [[...]] arguments. After checking thesymbol, code to compute the sequence to iterate over is generated.From then on the structure is similar to the structure of the otherloop code generators.%% **** do cmpSpecial instead of returning FALSE??<<inlining handler for [[for]] loops>>=setInlineHandler("for", function(e, cb, cntxt) {sym <- e[[2]]seq <- e[[3]]body <- e[[4]]if (! is.name(sym)) {## not worth warning here since the parser should not allow thisreturn(FALSE)}ncntxt <- make.nonTailCallContext(cntxt)cmp(seq, cb, ncntxt)ci <- cb$putconst(sym)callidx <- cb$putconst(e)<<generate context and body code for [[for]] loop>><<generate [[for]] loop wrap-up code>>TRUE})@ %defWhen a [[setjmp]] context is needed, the label given to [[STARTFOR]]is just the following instruction, which is a [[STARTLOOPCNTXT]]instruction. If the context is not needed then the label for the[[STARTFOR]] instruction will be the loop's [[STEPFOR]] instruction;if the context is needed then the first instruction in the code objectfor the body will be a [[GOTO]] instruction that jumps to the[[STEPFOR]] instruction. This design means the stepping and the jumpcan be handled by one instruction instead of two, a step instructionand a [[GOTO]].<<generate context and body code for [[for]] loop>>=if (checkSkipLoopCntxt(body, cntxt))cmpForBody(callidx, body, ci, cb, cntxt)else {cntxt$needRETURNJMP <- TRUE ## **** do this a better wayctxt.label <- cb$makelabel()cb$putcode(STARTFOR.OP, callidx, ci, ctxt.label)cb$putlabel(ctxt.label)code <- genCode(body, cntxt, ## **** expr isn't quite rightfunction(cb, cntxt) {cmpForBody(NULL, body, NULL, cb, cntxt)cb$putcode(ENDLOOPCNTXT.OP)})bi <- cb$putconst(code)cb$putcode(STARTLOOPCNTXT.OP, bi)}@ %defThe body code generator takes an additional argument, the index of theloop label. For the case where a [[setjmp]] context is needed thisargument is [[NULL]], and the first instruction generated is a[[GOTO]] targeting the [[STEPFOR]] instruction. This is labeled bythe [[loop.label]] label, since this will also be the target used by a[[next]] expression. An additional label, [[body.label]] is needed forthe top of the loop, which is used by [[STEPFOR]] if there are moreloop elements to process. When the [[ci]] argument is not [[NULL]]code is being generated for the case without a [[setjmp]] context, andthe first instruction is the [[STARTFOR]] instruction whichinitializes the loop and jumps to [[loop.label]] at the [[STEPLOOP]]instruction.<<[[cmpForBody]] function>>=cmpForBody <- function(callidx, body, ci, cb, cntxt) {body.label <- cb$makelabel()loop.label <- cb$makelabel()end.label <- cb$makelabel()if (is.null(ci))cb$putcode(GOTO.OP, loop.label)elsecb$putcode(STARTFOR.OP, callidx, ci, loop.label)cb$putlabel(body.label)lcntxt <- make.loopContext(cntxt, loop.label, end.label)cmp(body, cb, lcntxt)cb$putcode(POP.OP)cb$putlabel(loop.label)cb$putcode(STEPFOR.OP, body.label)cb$putlabel(end.label)}@ %def cmpForBodyThe wrap-up code issues an [[ENDFOR]] instruction instead of the[[LDNULL]] instruction used for [[repeat]] and [[while]] loops.<<generate [[for]] loop wrap-up code>>=cb$putcode(ENDFOR.OP)if (cntxt$tailcall) {cb$putcode(INVISIBLE.OP)cb$putcode(RETURN.OP)}@ %def\subsection{Avoiding runtime loop contexts}\label{subsec:skipcntxt}When all uses of [[break]] or [[next]] in a loop occur only in toplevel contexts then all [[break]] and [[next]] calls can beimplemented with simple [[GOTO]] instructions and a [[setjmp]] contextfor the loop is not needed. Top level contexts are the loop bodyitself and argument expressions in top level calls to [[if]], [[{]],and [[(]]. The [[switch]] functions will eventually be included as well.%% **** need to add switch to the top level functions%% **** may not be OK if switch uses vmpSpecial because of ... argThe function [[checkSkipLoopContxt]] recursively traverses anexpression tree to determine whether all relevant uses of [[break]] or[[next]] are safe to compile as [[GOTO]] instructions. The searchreturns [[FALSE]] if a [[break]] or [[next]] call occurs in an unsafeplace. The search stops and returns [[TRUE]] for any expression thatcannot contain relevant [[break]] or [[next]] calls. These stopexpressions are calls to the three loop functions and to [[function]].Calls to functions like [[quote]] that are known not to evaluate theirarguments could also be included among the stop functions but thisdoesn't seem particularly worth while at this time.The recursive checking function is defined as<<[[checkSkipLoopCntxt]] function>>=checkSkipLoopCntxt <- function(e, cntxt, breakOK = TRUE) {if (typeof(e) == "language") {fun <- e[[1]]if (typeof(fun) == "symbol") {fname <- as.character(fun)if (! breakOK && fname %in% c("break", "next"))FALSEelse if (isLoopStopFun(fname, cntxt))TRUEelse if (isLoopTopFun(fname, cntxt))checkSkipLoopCntxtList(e[-1], cntxt, breakOK)elsecheckSkipLoopCntxtList(e[-1], cntxt, FALSE)}elsecheckSkipLoopCntxtList(e, cntxt, FALSE)}else TRUE}@ %def checkSkipLoopCntxtA version that operates on a list of expressions is given by<<[[checkSkipLoopCntxtList]] function>>=checkSkipLoopCntxtList <- function(elist, cntxt, breakOK) {for (a in as.list(elist))if (! missing(a) && ! checkSkipLoopCntxt(a, cntxt, breakOK))return(FALSE)TRUE}@ %def checkSkipLoopCntxtListThe stop functions are identified by [[isLoopStopFun]]. This uses[[isBaseVar]] to ensure that interpreting a reference to a stopfunction name as referring to the corresponding function in the[[base]] package is permitted by the current optimization settings.%% **** could also stop for quote() and some others.<<[[isLoopStopFun]] function>>=isLoopStopFun <- function(fname, cntxt)(fname %in% c("function", "for", "while", "repeat") &&isBaseVar(fname, cntxt))@ %def isLoopStopFunThe top level functions are identified by [[isLoopTopFun]]. Again thecompilation context is consulted to ensure that candidate can beassumed to be from the [[base]] package.%% **** eventually add "switch"<<[[isLoopTopFun]] function>>=isLoopTopFun <- function(fname, cntxt)(fname %in% c("(", "{", "if") &&isBaseVar(fname, cntxt))@ %def isLoopTopFunThe [[checkSkipLoopCntxt]] function does not check whether calls to[[break]] or [[next]] are indeed calls to the [[base]] functions.Given the special syntactic nature of [[break]] and [[next]] this isvery unlikely to cause problems, but if it does it will result in somesafe loops being considered unsafe and so errs in the conservativedirection.\section{More inlining}\subsection{Basic arithmetic expressions}The addition and subtraction functions [[+]] and [[-]] are [[BUILTIN]]functions that can both be called with one or two arguments.Multiplication and division functions [[*]] and [[/]] require twoarguments. Since code generation for all one arguments cases and alltwo argument cases is very similar these are abstracted out intofunctions [[cmpPrim1]] and [[cmpPrim2]].The code generators for addition and subtraction are given by<<inline handlers for [[+]] and [[-]]>>=setInlineHandler("+", function(e, cb, cntxt) {if (length(e) == 3)cmpPrim2(e, cb, ADD.OP, cntxt)elsecmpPrim1(e, cb, UPLUS.OP, cntxt)})setInlineHandler("-", function(e, cb, cntxt) {if (length(e) == 3)cmpPrim2(e, cb, SUB.OP, cntxt)elsecmpPrim1(e, cb, UMINUS.OP, cntxt)})@ %defThe code generators for multiplication and division are<<inline handlers for [[*]] and [[/]]>>=setInlineHandler("*", function(e, cb, cntxt)cmpPrim2(e, cb, MUL.OP, cntxt))setInlineHandler("/", function(e, cb, cntxt)cmpPrim2(e, cb, DIV.OP, cntxt))@ %defCode for instructions corresponding to calls to a [[BUILTIN]] functionwith one argument are generated by [[cmpPrim1]]. The generatorproduces code for a generic [[BUILTIN]] call using [[cmpBuiltin]] ifif there are any missing or [[...]] arguments or if the number ofarguments is not equal to one. Otherwise code for the argument isgenerated in a non-tail-call context, and the instruction provided asthe [[op]] argument is emitted followed by a [[RETURN]] instructionfor an expression in tail position. The [[op]] instructions take thecall as operand for use in error message and for internal dispatching.<<[[cmpPrim1]] function>>=cmpPrim1 <- function(e, cb, op, cntxt) {if (dots.or.missing(e[-1]))cmpBuiltin(e, cb, cntxt)else if (length(e) != 2) {notifyWrongArgCount(e[[1]], cntxt)cmpBuiltin(e, cb, cntxt)}else {ncntxt <- make.nonTailCallContext(cntxt)cmp(e[[2]], cb, ncntxt);ci <- cb$putconst(e)cb$putcode(op, ci)if (cntxt$tailcall)cb$putcode(RETURN.OP)TRUE}}@ %def cmpPrim1Code generation for the two argument case is similar, except that thesecond argument has to be compiled with an argument context since thestack already has the value of the first argument on it and that wouldneed to be popped before a jump.<<[[cmpPrim2]] function>>=cmpPrim2 <- function(e, cb, op, cntxt) {if (dots.or.missing(e[-1]))cmpBuiltin(e, cb, cntxt)else if (length(e) != 3) {notifyWrongArgCount(e[[1]], cntxt)cmpBuiltin(e, cb, cntxt)}else {ncntxt <- make.nonTailCallContext(cntxt)cmp(e[[2]], cb, ncntxt);ncntxt <- make.argContext(cntxt)cmp(e[[3]], cb, ncntxt)ci <- cb$putconst(e)cb$putcode(op, ci)if (cntxt$tailcall)cb$putcode(RETURN.OP)TRUE}}@ %def cmpPrim2Calls to the power function [[^]] and the functions [[exp]] and[[sqrt]] can be compiled using [[cmpPrim1]] and [[cmpPrim2]] as well:<<inline handlers for [[^]], [[exp]], and [[sqrt]]>>=setInlineHandler("^", function(e, cb, cntxt)cmpPrim2(e, cb, EXPT.OP, cntxt))setInlineHandler("exp", function(e, cb, cntxt)cmpPrim1(e, cb, EXP.OP, cntxt))setInlineHandler("sqrt", function(e, cb, cntxt)cmpPrim1(e, cb, SQRT.OP, cntxt))@The [[log]] function is currently defined as a [[SPECIAL]]. Theinline handler for [[log]] is thus defined by<<inline handler for [[log]]>>=setInlineHandler("log", cmpSpecial)@It would be a good idea to add instructions for one and two argument logfunctions.\subsection{Logical operators}Two argument instructions are provided for the comparison operatorsand code for them can be generated using [[cmpPrim2]]:<<inline handlers for comparison operators>>=setInlineHandler("==", function(e, cb, cntxt)cmpPrim2(e, cb, EQ.OP, cntxt))setInlineHandler("!=", function(e, cb, cntxt)cmpPrim2(e, cb, NE.OP, cntxt))setInlineHandler("<", function(e, cb, cntxt)cmpPrim2(e, cb, LT.OP, cntxt))setInlineHandler("<=", function(e, cb, cntxt)cmpPrim2(e, cb, LE.OP, cntxt))setInlineHandler(">=", function(e, cb, cntxt)cmpPrim2(e, cb, GE.OP, cntxt))setInlineHandler(">", function(e, cb, cntxt)cmpPrim2(e, cb, GT.OP, cntxt))@ %defThe vectorized [[&]] and [[|]] functions are handled similarly:<<inline handlers for [[&]] and [[|]]>>=setInlineHandler("&", function(e, cb, cntxt)cmpPrim2(e, cb, AND.OP, cntxt))setInlineHandler("|", function(e, cb, cntxt)cmpPrim2(e, cb, OR.OP, cntxt))@ %defThe negation operator [[!]] takes only one argument and code for callsto it are generated using [[cmpPrim1]]:<<inline handler for [[!]]>>=setInlineHandler("!", function(e, cb, cntxt)cmpPrim1(e, cb, NOT.OP, cntxt))@ %def%% **** do log() somewhere around here?%% **** is log(x,) == log(x)???%% **** is log(,y) allowed?\subsection{Subsetting and related operations}\label{subsec:subset}Current R semantics are such that the subsetting operator [[[]] and anumber of others may not evaluate some of their arguments if S3 or S4methods are available. S-plus has different semantics---there thesubsetting operator is guaranteed to evaluate its arguments.% In the case of the concatenation function [[c]] it is not clear% whether these semantics are worth preserving; changing [[c]] to a% [[BUILTIN]] seems to cause no problems on [[CRAN]] and [[BioC]]% packages tested.For subsetting there are [[CRAN]] packages that use non-standardevaluation of their arguments ([[igraph]] is one example), so thisprobably can no longer be changed.The compiler preserve these semantics. To do so subsetting isimplemented in terms of two instructions, [[STARTSUBSET]] and[[DFLTSUBSET]]. The object being subsetted is evaluated and placed onthe stack. [[STARTSUBSET]] takes a constant table index for theexpression and a label operand as operands and examines the object onthe stack. If an internal S3 or S4 dispatch succeeds then thereceiver object is removed and the result is placed on the stack and ajump to the label is carried out. If the dispatch fails then code toevaluate and execute the arguments is executed followed by a[[DFLTSUBSET]] instruction.This pattern is used for several other operations and is abstractedinto the code generation function [[cmpDispatch]]. Code for subsettingand other operations is then generated by<<inlining handlers for some dispatching SPECIAL functions>>=setInlineHandler("[", function(e, cb, cntxt)cmpDispatch(STARTSUBSET.OP, DFLTSUBSET.OP, e, cb, cntxt))# **** c() is now a BUILTIN# setInlineHandler("c", function(e, cb, cntxt)# cmpDispatch(STARTC.OP, DFLTC.OP, e, cb, cntxt, FALSE))setInlineHandler("[[", function(e, cb, cntxt)cmpDispatch(STARTSUBSET2.OP, DFLTSUBSET2.OP, e, cb, cntxt))@The [[cmpDispatch]] function takes the two opcodes as arguments. Itdeclines to handle cases with [[...]] arguments in the call or with amissing first argument --- these will be handled as calls to a[[SPECIAL]] primitive. For the case handled it generates code for thefirst argument, followed by a call to the first [[start.op]]instruction. The operands for the [[start.op]] are a constant poolindex for the expression and a label for the instruction following the[[dflt.op]] instruction that allows skipping over the default casecode. The default case code consists of code to compute and push thearguments followed by the [[dflt.op]] instruction.<<[[cmpDispatch]] function>>=cmpDispatch <- function(start.op, dflt.op, e, cb, cntxt, missingOK = TRUE) {if ((missingOK && any.dots(e)) ||(! missingOK && dots.or.missing(e)) ||length(e) == 1)cmpSpecial(e, cb, cntxt) ## puntelse {ne <- length(e)oe <- e[[2]]if (missing(oe))cmpSpecial(e, cb, cntxt) ## puntelse {ncntxt <- make.argContext(cntxt)cmp(oe, cb, ncntxt)ci <- cb$putconst(e)end.label <- cb$makelabel()cb$putcode(start.op, ci, end.label)if (ne > 2)cmpBuiltinArgs(e[-(1:2)], names(e)[-(1:2)], cb, cntxt,missingOK)cb$putcode(dflt.op)cb$putlabel(end.label)if (cntxt$tailcall) cb$putcode(RETURN.OP)TRUE}}}@ %def cmpDispatch%% **** The implementation currently implies that arguments to things%% **** with S4 methods may be evaluated more than once if dispatch%% **** does not happen. It would be better to rewrite this so if%% **** arguments are evaluated we stay with the interpreted version%% **** all the way. This requires a bit of refactoring of%% **** DispatchOrEval code to get it to work. But it should not%% **** affect the compiler.%% ****%% **** There may be some merit to always go with the interpreted code%% **** if the receiver has the object bit set -- that way the%% **** sequence coule de done as%% ****%% **** if (object bit set)%% **** CALLSPECIAL%% **** else%% **** do default thing%% ****%% **** and in some cases the object bit test can be hoisted.The [[$]] function is simpler to implement since its selector argumentis never evaluated. The [[DOLLAR]] instruction takes the object toextract a component from off the stack and takes a constant indexargument specifying the selection symbol.%% signal warning if selector is not a symbol or a string??%% also decline if any missing args?<<inlining handler for [[$]]>>=setInlineHandler("$", function(e, cb, cntxt) {if (any.dots(e) || length(e) != 3)cmpSpecial(e, cb, cntxt)else {sym <- if (is.character(e[[3]]))as.name(e[[3]]) else e[[3]]if (is.name(sym)) {ncntxt <- make.argContext(cntxt)cmp(e[[2]], cb, ncntxt)ci <- cb$putconst(e)csi <- cb$putconst(sym)cb$putcode(DOLLAR.OP, ci, csi)if (cntxt$tailcall) cb$putcode(RETURN.OP)TRUE}else cmpSpecial(e, cb, cntxt)}})@ %def\subsection{Inlining simple [[.Internal]] functions}A number of functions are defined as simple wrappers around[[.Internal]] calls. One example is [[dnorm]], which is currentlydefined as\begin{verbatim}dnorm <- function (x, mean = 0, sd = 1, log = FALSE).Internal(dnorm(x, mean, sd, log))\end{verbatim}The implementation of [[.Internal]] functions can be of type[[BUILTIN]] or [[SPECIAL]]. The [[dnorm]] implementation is of type[[BUILTIN]], so its arguments are guaranteed to be evaluated in order,and this particular function doe not depend on the position of itscalls in the evaluation stack. As a result, a call of the form\begin{verbatim}dnorm(2, 1)\end{verbatim}can be replaced by the call\begin{verbatim}.Internal(dnorm(2, 1, 1, FALSE))\end{verbatim}%% **** except for error messages maybe??This can result in considerable speed-up since it avoids the overheadof the call to the wrapper function.The substitution of a call to the wrapper with a [[.Internal]] callcan be done by a function [[inlineSimpleInternalCall]] defined as<<[[inlineSimpleInternalCall]] function>>=inlineSimpleInternalCall <- function(e, def) {if (! dots.or.missing(e) && is.simpleInternal(def)) {forms <- formals(def)fnames <- names(forms)b <- body(def)if (typeof(b) == "language" && length(b) == 2 && b[[1]] == "{")b <- b[[2]]icall <- b[[2]]defaults <- forms ## **** could strip missings but OK not to?cenv <- c(as.list(match.call(def, e, F))[-1], defaults)subst <- function(n)if (typeof(n) == "symbol") cenv[[as.character(n)]] else nargs <- lapply(as.list(icall[-1]), subst)as.call(list(quote(.Internal), as.call(c(icall[[1]], args))))}else NULL}@ %def inlineSimpleInternalCallCode for an inlined simple internal function can then be generated by[[cmpSimpleInternal]]:<<[[cmpSimpleInternal]] function>>=cmpSimpleInternal <- function(e, cb, cntxt) {if (any.dots(e))FALSEelse {name <- as.character(e[[1]])def <- findFunDef(name, cntxt)if (! checkCall(def, e, NULL)) return(FALSE)call <- inlineSimpleInternalCall(e, def)if (is.null(call))FALSEelse {cmp(call, cb, cntxt)TRUE}}}@ %def cmpSimpleInternal<<inline safe simple [[.Internal]] functions from [[base]]>>=safeBaseInternals <- c("atan2", "besselY", "beta", "choose","drop", "inherits", "is.vector", "lbeta", "lchoose","nchar", "polyroot", "typeof", "vector", "which.max","which.min", "is.loaded", "identical")for (i in safeBaseInternals) setInlineHandler(i, cmpSimpleInternal)@ %def safeBaseInternals%% **** nextn would also be OK with a broader definition of 'safe'<<inline safe simple [[.Internal]] functions from [[stats]]>>=safeStatsInternals <- c("dbinom", "dcauchy", "dgeom", "dhyper", "dlnorm","dlogis", "dnorm", "dpois", "dunif", "dweibull","fft", "mvfft", "pbinom", "pcauchy","pgeom", "phyper", "plnorm", "plogis", "pnorm","ppois", "punif", "pweibull", "qbinom", "qcauchy","qgeom", "qhyper", "qlnorm", "qlogis", "qnorm","qpois", "qunif", "qweibull", "rbinom", "rcauchy","rgeom", "rhyper", "rlnorm", "rlogis", "rnorm","rpois", "rsignrank", "runif", "rweibull","rwilcox", "ptukey", "qtukey")for (i in safeStatsInternals) setInlineHandler(i, cmpSimpleInternal, "stats")@ %defIt is possible to automate the process of identifying functions withthe simple wrapper form and with [[.Internal]] implementations of type[[BUILTIN]], and the function [[simpleInternals]] produces a list ofsuch candidates for a given package on the search path. Butdetermining whether such a candidate can be safely inlined needs to bedone manually. Most can, but some, such as [[sys.call]], cannot sincethey depend on their position on the call stack (removing the wrappercall that the implementation expects would change the result).Nevertheless, [[simpleInternals]] is useful for providing a list ofcandidates to screen. The [[is.simpleInternal]] function can be usedin test code to check that the assumption made in the compiler isvalid. The implementation is<<[[simpleInternals]] function>>=simpleInternals <- function(pos = "package:base") {names <- ls(pos = pos, all = T)if (length(names) == 0)character(0)else {fn <- function(n)is.simpleInternal(get(n, pos = pos))names[sapply(names, fn)]}}@ %def simpleInternals<<[[is.simpleInternal]] function>>=is.simpleInternal <- function(def) {if (typeof(def) == "closure" && simpleFormals(def)) {b <- body(def)if (typeof(b) == "language" && length(b) == 2 && b[[1]] == "{")b <- b[[2]]if (typeof(b) == "language" &&typeof(b[[1]]) == "symbol" &&b[[1]] == ".Internal") {icall <- b[[2]]ifun <- icall[[1]]typeof(ifun) == "symbol" &&.Internal(is.builtin.internal(as.name(ifun))) &&simpleArgs(icall, names(formals(def)))}else FALSE}else FALSE}@ %def is.simpleInternal<<[[simpleFormals]] function>>=simpleFormals <- function(def) {forms <- formals(def)if ("..." %in% names(forms))return(FALSE)for (d in as.list(forms)) {if (! missing(d)) {## **** check constant foldingif (typeof(d) %in% c("symbol", "language", "promise", "bytecode"))return(FALSE)}}TRUE}@ %def simpleFormals<<[[simpleArgs]] function>>=simpleArgs <- function(icall, fnames) {for (a in as.list(icall[-1])) {if (missing(a))return(FALSE)else if (typeof(a) == "symbol") {if (! (as.character(a) %in% fnames))return(FALSE)}else if (typeof(a) %in% c("language", "promise", "bytecode"))return(FALSE)}TRUE}@ %def simpleArgs\subsection{Inlining [[is.xyz]] functions}Most of the [[is.xyz]] functions in [[base]] are simple [[BUILTIN]]sthat do not do internal dispatch. They have simple instructionsdefined for them and are compiled in a common way. [[cmpIs]] abstractout the common compilation process.<<[[cmpIs]] function>>=cmpIs <- function(op, e, cb, cntxt) {if (any.dots(e) || length(e) != 2)cmpBuiltin(e, cb, cntxt)else {## **** check that the function is a builtin somewhere??s<-make.argContext(cntxt)cmp(e[[2]], cb, s)cb$putcode(op)if (cntxt$tailcall) cb$putcode(RETURN.OP)TRUE}}@ %def cmpIsInlining handlers are then defined by<<inlining handlers for [[is.xyz]] functions>>=setInlineHandler("is.character", function(e, cb, cntxt)cmpIs(ISCHARACTER.OP, e, cb, cntxt))setInlineHandler("is.complex", function(e, cb, cntxt)cmpIs(ISCOMPLEX.OP, e, cb, cntxt))setInlineHandler("is.double", function(e, cb, cntxt)cmpIs(ISDOUBLE.OP, e, cb, cntxt))setInlineHandler("is.integer", function(e, cb, cntxt)cmpIs(ISINTEGER.OP, e, cb, cntxt))setInlineHandler("is.logical", function(e, cb, cntxt)cmpIs(ISLOGICAL.OP, e, cb, cntxt))setInlineHandler("is.name", function(e, cb, cntxt)cmpIs(ISSYMBOL.OP, e, cb, cntxt))setInlineHandler("is.null", function(e, cb, cntxt)cmpIs(ISNULL.OP, e, cb, cntxt))setInlineHandler("is.object", function(e, cb, cntxt)cmpIs(ISOBJECT.OP, e, cb, cntxt))setInlineHandler("is.real", function(e, cb, cntxt)cmpIs(ISDOUBLE.OP, e, cb, cntxt))setInlineHandler("is.symbol", function(e, cb, cntxt)cmpIs(ISSYMBOL.OP, e, cb, cntxt))@ %defAt present [[is.numeric]], [[is.matrix]], and [[is.array]] do internaldispatching so we just handle them as ordinary [[BUILTIN]]s. It mightbe worth defining virtual machine instructions for them as well.\subsection{Inlining handlers for controlling warnings}The inlining handlers in this section do not actually affect codegeneration. Their purpose is to suppress warnings.Compiling calls to the [[::]] and [[:::]] functions without specialhandling would generate undefined variable warnings for the arguments.This is avoided by converting the arguments from symbols to strings,which these functions would do anyway at runtime, and then compilingthe modified calls. The common process is handled by [[cmpMultiColon]].<<[[cmpMultiColon]] function>>=cmpMultiColon <- function(e, cb, cntxt) {if (! dots.or.missing(e) && length(e) == 3) {goodType <- function(a)typeof(a) == "symbol" ||(typeof(a) == "character" && length(a) == 1)fun <- e[[1]]x <- e[[2]]y <- e[[3]]if (goodType(x) && goodType(y)) {args <- list(as.character(x), as.character(y))cmpCallSymFun(fun, args, e, cb, cntxt)TRUE}else FALSE}else FALSE}@ %def cmpMultiColonCode generators are then registered by<<inlining handlers for [[::]] and [[:::]]>>=setInlineHandler("::", cmpMultiColon)setInlineHandler(":::", cmpMultiColon)@Calls to with will often generate spurious undefined variable warningfor variables appearing in the expression argument. A crude approachis to compile the entire call with undefined variable warningssuppressed.<<inlining handler for [[with]]>>=setInlineHandler("with", function(e, cb, cntxt) {cntxt$suppressUndefined <- TRUEcmpCallSymFun(e[[1]], e[-1], e, cb, cntxt)TRUE})@A similar issue arises for [[require]], where an unquoted argument isoften used.<<inlining handler for [[require]]>>=setInlineHandler("require", function(e, cb, cntxt) {cntxt$suppressUndefined <- TRUEcmpCallSymFun(e[[1]], e[-1], e, cb, cntxt)TRUE})@\section{The [[switch]] function}The [[switch]] function has somewhat awkward semantics that varydepending on whether the value of the first argument is a characterstring or is numeric. For a string all or all but one of thealternatives must be named, and empty case arguments are allowed andresult in falling through to the next non-empty case. In the numericcase selecting an empty case produces an error. If there is more thanone alternative case and no cases are named then a character selectorargument will produce an error, so one can assume that a numericswitch is intended. But a [[switch]] with named arguments can be usedwith a numeric selector, so it is not in general possible to determinethe intended type of the [[switch]] call from the structure of thecall alone. The compiled code therefore has to allow for bothpossibilities.The inlining handler goes through a number of steps collecting andprocessing information computed from the call and finally emits codefor the non-empty alternatives. If the [[switch]] expression appearsin tail position then each alternative will end in a [[RETURN]]instruction. If the call is not in tail position then eachalternative will end with a [[GOTO]] than jumps to a label placedafter the code for the final alternative.<<inline handler for [[switch]]>>=setInlineHandler("switch", function(e, cb, cntxt) {if (length(e) < 2 || any.dots(e))cmpSpecial(e, cb, cntxt)else {## **** check name on EXPR, if any, partially matches EXPR?<<extract the [[switch]] expression components>><<collect information on named alternatives>><<create the labels>><<create the map from names to labels for a character switch>><<emit code for the [[EXPR]] argument>><<emit the switch instruction>><<emit error code for empty alternative in numerical switch>><<emit code for the default case>><<emit code for non-empty alternatives>>if (! cntxt$tailcall)cb$putlabel(endLabel)}TRUE})@The first step in processing the [[switch]] expression is to extractthe selector expression [[expr]] and the case expressions, to identifywhich, if any, of the cases are empty, and to extract the names of thecases as [[nm]]. If there is only one case and that case is not namedthen setting [[nm = ""]] allows this situation to be processed by codeused when names are present.<<extract the [[switch]] expression components>>=expr <- e[[2]]cases <-e[-c(1, 2)]miss <- missingArgs(cases)nm <- names(cases)## allow for corner cases like switch(x, 1) which always## returns 1 if x is a character scalar.if (is.null(nm) && length(cases) == 1)nm <- ""@ %defThe next step in the case where some cases are named is to check for adefault expression. If there is more than one expression then the[[switch]] is compiled by [[cmpSpecial]]. This avoids having toreproduce the runtime error that would be generated if the [[switch]]is called with a character selector.%% **** would probably be better to not punth though -- then we could%% **** allow break/next to use GOTO<<collect information on named alternatives>>=## collect information on named alternatives and check for## multiple default cases.if (! is.null(nm)) {haveNames <- TRUEndflt <- sum(nm == "")if (ndflt > 1) {notifyMultipleSwitchDefaults(ndflt, cntxt)## **** punt back to interpreted version for now to get## **** runtime error message for multiple defaultscmpSpecial(e, cb, cntxt)return(TRUE)}if (ndflt > 0)haveCharDflt <- TRUEelsehaveCharDflt <- FALSE}else {haveNames <- FALSEhaveCharDflt <- FALSE}@ %defNext the labels are generated. [[missLabel]] will be the label forcode that signals an error if a numerical selector expression choosesa case with an empty argument. The label [[dfltLabel]] will be forcode that invisibly procures the value [[NULL]], which is the defaultcase for a numerical selector argument and also for a characterselector when no unnamed default case is provided. All non-empty casesare given their own labels, and [[endLabel]] is generated if it willbe needed as the [[GOTO]] target for a [[switch]] expression that isnot in tail position.<<create the labels>>=## create the labelsif (any(miss))missLabel <- cb$makelabel()dfltLabel <- cb$makelabel()lab <- function(m)if (m) missLabelelse cb$makelabel()labels <- c(lapply(miss, lab), list(dfltLabel))if (! cntxt$tailcall)endLabel <- cb$makelabel()@ %defWhen there are named cases a map from the case names to thecorresponding code labels is constructed next. If no unnamed defaultwas provided one is added that uses the [[dfltLabel]].<<create the map from names to labels for a character switch>>=## create the map from names to labels for a character switchif (haveNames) {unm <- unique(nm[nm != ""])if (haveCharDflt)unm <- c(unm, "")nlabels <- labels[unlist(lapply(unm, findActionIndex, nm, miss))]## if there is no unnamed case to act as a default for a## character switch then the numeric default becomes the## character default as well.if (! haveCharDflt) {unm <- c(unm, "")nlabels <- c(nlabels, list(dfltLabel))}}else {unm <- NULLnlabels <- NULL}@ %defThe computation of the index of the appropriate label for a given nameis carried out by [[findActionIndex]].%% **** rewrite this to directly return the label?<<[[findActionIndex]] function>>=findActionIndex <- function(name, nm, miss) {start <- match(name, nm)aidx <- c(which(! miss), length(nm) + 1)min(aidx[aidx >= start])}@ %def findActionIndexAt this point we are ready to start emitting code into the codebuffer. First code to compute the selector is emitted. As with thecondition for an [[if]] expression a non-tail-call context is used.<<emit code for the [[EXPR]] argument>>=## compile the EXPR argumentncntxt <- make.nonTailCallContext(cntxt)cmp(expr, cb, ncntxt)@ %defThe switch instruction takes the selector off the stack and fouroperands form the instruction stream: the call index, an index for thenames, or [[NULL]] if there are none, and indices for the labels for acharacter selector and for a numeric selector. At this point lists oflabels are placed in the instruction buffer. At code extraction timethese will be replaced by indices for numeric offset vectors by the[[patchlables]] function of the code buffer.<<emit the switch instruction>>=## emit the SWITCH instructioncei <- cb$putconst(e)if (haveNames) {cni <- cb$putconst(unm)cb$putcode(SWITCH.OP, cei, cni, nlabels, labels)}else {cni <- cb$putconst(NULL)cb$putcode(SWITCH.OP, cei, cni, cni, labels)}@ %defIf there are empty alternatives then code to signal an error for anumeric selector that chooses one of these is needed and isidentified by the label [[missLabel]].<<emit error code for empty alternative in numerical switch>>=## emit code to signal an error if a numeric switch hist an## empty alternative (fall through, as for character, might## make more sense but that isn't the way switch() works)if (any(miss)) {cb$putlabel(missLabel)cmp(quote(stop("empty alternative in numeric switch")), cb, cntxt)}@ %defCode for the numeric default case, corresponding to [[dfltLabel]],places [[NULL]] on the stack, and for a [[switch]] in tail positionthis is followed by an [[INVISIBLE]] and a [[RETURN]] instruction.<<emit code for the default case>>=## emit code for the default casecb$putlabel(dfltLabel)cb$putcode(LDNULL.OP)if (cntxt$tailcall) {cb$putcode(INVISIBLE.OP)cb$putcode(RETURN.OP)}elsecb$putcode(GOTO.OP, endLabel)@ %defFinally the labels and code for the non-empty alternatives are writtento the code buffer. In non-tail position the code is followed by a[[GOTO]] instruction that jumps to [[endLabel]]. The final case doesnot need this [[GOTO]].%% **** maybe try to drop the final GOTO<<emit code for non-empty alternatives>>=## emit code for the non-empty alternativesfor (i in seq_along(cases)) {if (! miss[i]) {cb$putlabel(labels[[i]])cmp(cases[[i]], cb, cntxt)if (! cntxt$tailcall)cb$putcode(GOTO.OP, endLabel)}}@ %def\section{Assignments expressions}R supports simple assignments in which the left-hand side of theassignment expression is a symbol and complex assignments of the form\begin{verbatim}f(x) <- v\end{verbatim}or\begin{verbatim}g(f(x)) <- v\end{verbatim}The second form is sometimes called a nested complex assignment.Ordinary assignment creates or modifies a binding in the currentenvironment. Superassignment via the [[<<-]] operator modifies abinding in a containing environment.Assignment expressions are compiled by [[cmpAssign]]. This functionchecks the form of the assignment expression and, for well formedexpressions then uses [[cmpSymbolAssign]] for simple assignments and[[cmpComplexAssign]] for complex assignments.<<[[cmpAssign]] function>>=cmpAssign <- function(e, cb, cntxt) {if (! checkAssign(e, cntxt))return(cmpSpecial(e, cb, cntxt))superAssign <- as.character(e[[1]]) == "<<-"lhs <- e[[2]]value <- e[[3]]symbol <- as.name(getAssignedVar(e))if (superAssign && ! findVar(symbol, cntxt))notifyNoSuperAssignVar(symbol, cntxt)if (is.name(lhs) || is.character(lhs))cmpSymbolAssign(symbol, value, superAssign, cb, cntxt)else if (typeof(lhs) == "language")cmpComplexAssign(symbol, lhs, value, superAssign, cb, cntxt)else cmpSpecial(e, cb, cntxt) # punt for now}@ %def cmpAssignThe code generators for the assignment operators [[<-]] and [[=]] andthe superassignment operator [[<<-]] are registered by<<inlining handlers for [[<-]], [[=]], and [[<<-]]>>=setInlineHandler("<-", cmpAssign)setInlineHandler("=", cmpAssign)setInlineHandler("<<-", cmpAssign)@ %defThe function [[checkAssign]] is used to check that an assignmentexpression is well-formed.<<[[checkAssign]] function>>=checkAssign <- function(e, cntxt) {if (length(e) != 3)FALSEelse {place <- e[[2]]if (typeof(place) == "symbol" ||(typeof(place) == "character" && length(place) == 1))TRUEelse {<<check left hand side call>>}}}@ %def checkAssignA valid left hand side call must have a function that is either asymbol or is of the form [[foo::bar]] or [[foo:::bar]], and the firstargument must be a symbol or another valid left hand side call. A[[while]] loop is used to unravel nested calls.<<check left hand side call>>=while (typeof(place) == "language") {fun <- place[[1]]if (typeof(fun) != "symbol" &&! (typeof(fun) == "language" && length(fun) == 3 &&typeof(fun[[1]]) == "symbol" &&as.character(fun[[1]]) %in% c("::", ":::"))) {notifyBadAssignFun(fun, cntxt)return(FALSE)}place = place[[2]]}if (typeof(place) == "symbol")TRUEelse FALSE@ %def\subsection{Simple assignment expressions}%% **** handle fun defs specially for message purposes??Code for assignment to a symbol is generated by [[cmpSymbolAssign]].<<[[cmpSymbolAssign]] function>>=cmpSymbolAssign <- function(symbol, value, superAssign, cb, cntxt) {<<compile the right hand side value expression>><<emit code for the symbol assignment instruction>><<for tail calls return the value invisibly>>TRUE}@ %def cmpSymbolAssignA non-tail-call context is used to generate code for the right handside value expression.<<compile the right hand side value expression>>=ncntxt <- make.nonTailCallContext(cntxt)cmp(value, cb, ncntxt)@ %defThe [[SETVAR]] and [[SETVAR2]] instructions assign the value on thestack to the symbol specified by its constant pool index operand. The[[SETVAR]] instruction is used by ordinary assignment to assign in thelocal frame, and [[SETVAR2]] for superassignments.<<emit code for the symbol assignment instruction>>=ci <- cb$putconst(symbol)if (superAssign) {if (! findVar(symbol, cntxt))notifyNoSuperAssignVar(symbol, cntxt)cb$putcode(SETVAR2.OP, ci)}elsecb$putcode(SETVAR.OP, ci)@ %defThe [[SETVAR]] and [[SETVAR2]] instructions leave the value on thestack as the value of the assignment expression; if the expressionappears in tail position then this value is returned with the visibleflag set to [[FALSE]].<<for tail calls return the value invisibly>>=if (cntxt$tailcall) {cb$putcode(INVISIBLE.OP)cb$putcode(RETURN.OP)}@ %def\subsection{Complex assignment expressions}\label{subsec:complexassign}It seems somehow appropriate at this point to mention that the code in[[eval.c]] implementing the interpreter semantics starts with thefollowing comment:\begin{verbatim}/** Assignments for complex LVAL specifications. This is the stuff that* nightmares are made of ...\end{verbatim}There are some issues with the semantics for complex assignment asimplemented by the interpreter:\begin{itemize}\item With the current approach the following legal, though strange,code fails:<<inner assignment trashes temporary>>=f <-function(x, y) x`f<-` <- function(x, y, value) { y; x}x <- 1y <- 2f(x, y[] <- 1) <- 3@ %defThe reason is that the current left hand side object is maintained ina variable [[*tmp]], and processing the assignment in the secondargument first overwrites the value of [[*tmp*]] and then removes[[*tmp*]] before the first argument is evaluated. Using evaluatedpromises as arguments, as is done for the right hand side value,solves this.\item The current approach of using a temporary variable [[*tmp*]] tohold the evaluated LHS object requires an internal cleanup contextto ensure that the variable is removed in the event of a non-localexit. Implementing this in the compiler would introduce significantoverhead.\item The asymmetry of handling the pre-evaluated right hand sidevalue via an evaluated promise and the pre-evaluated left hand sidevia a temporary variable makes the code harder to understand and thesemantics harder to explain.\item Using promises in an expression passed to eval means promisescan leak out into R via sys.call. This is something he have tried toavoid and should try to avoid so we can have the freedom toimplement lazy evaluation differently if that seems useful. [It maybe possible at some point to avoid allocation of promise objects incompiled code.] The compiler can avoid this by using promises onlyin the argument lists passed to function calls, not in the callexpressions. A similar change could be made in the interpreter butit would have a small runtime penalty for constructing an expressionin addition to an argument list I would prefer to avoid that for nowuntil the compiler has been turned on by default.\item The current approach of installing the intermediate RHS value asthe expression for the RHS promise in nested complex assignments hasseveral drawbacks:\begin{itemize}\item it can produce huge expressions.\item the result is misleading if the intermediate RHS value is asymbol or a language object.\item to maintain this in compiled code it would be necessary toconstruct the assignment function call expression at runtime eventhough it is usually not needed (or it would require significantrewriting to allow on-demand computation of the call). If *vtmp*is used as a marker for the expression and documented as not areal variable then the call can be constructed at compile time.\end{itemize}\item In nested complex assignments the additional arguments of theinner functions are evaluated twice. This is illustrated by runningthis code:<<multiple evaluation of arguments in assignments>>=f <- function(x, y) {y ; x }`f<-` <- function(x, y, value) { y; x }g <- function(x, y) {y ; x }`g<-` <- function(x, y, value) { y; x }x <- 1y <- 2f(g(x, print(y)), y) <- 3@ %defThis is something we have lived with, and I don't propose to changeit at this time. But it would be good to be able to change it in thefuture.\end{itemize}Because of these issues the compiler implements slightly differentsemantics for complex assignment than the current intepreter.\emph{Evaluation} semantics should be identical; the difference arisesin how intermediate values are managed and has some effect on resultsproduced by [[substitute]]. In particular, no intermediate [[*tmp*]]value is used and therefore no cleanup frame is needed. This doesmean that uses of the form\begin{verbatim}eval(substitute(<first arg>), parent.frame())\end{verbatim}will no longer work. In tests of most of CRAN and BioC this directlyaffects only one function, [[$.proto]] in the [[proto]] package, andindirectly about 30 packages using proto fail. I have looked at the[[$.proto]] implementation, and it turns out that the[[eval(substitute())]] approach used there can be replaced by standardevaluation using lexical scope. This produces better code, and theresult works with both current R and the proposed changes (proto andall the dependent packages pass check with this change). The[[proto]] maintainer has agreed to change [[proto]] along these linesbut as of the time of writing has not yet done so. Once this is done,or once R 2.13 is released, I intend to change the intepreter so useevaluated promises in place of the [[*tmp*]] variable to bring thecompiled and interpreted semantics closer together.Complex assignment expressions are compiled by [[cmpComplexAssign]].<<[[cmpComplexAssign]] function>>=cmpComplexAssign <- function(symbol, lhs, value, superAssign, cb, cntxt) {<<select complex assignment instructions>><<compile the right hand side value expression>><<compile the left hand side call>><<for tail calls return the value invisibly>>TRUE;}@ %def cmpComplexAssignAssignment code is bracketed by a start and an end instruction.<<compile the left hand side call>>=csi <- cb$putconst(symbol)cb$putcode(startOP, csi)<<compile code to compute left hand side values>><<compile code to compute right hand side values>>cb$putcode(endOP, csi)@ %defThe appropriate instructions [[startOP]] and [[endOP]] depend onwhether the assignment is an ordinary assignment or a superassignment.<<select complex assignment instructions>>=if (superAssign) {startOP <- STARTASSIGN2.OPendOP <- ENDASSIGN2.OP}else {if (! findVar(symbol, cntxt))notifyUndefVar(symbol, cntxt)startOP <- STARTASSIGN.OPendOP <- ENDASSIGN.OP}@ %defAn undefined variable notification is issued for ordinary assignment,since this will produce a runtime error. For superassignment[[cmpAssign]] has already checked for an undefined left-hand-sidevariable and issued a notification if none was found.The start instructions obtain the initial value of the left-hand-sidevariable and in the case of standard assignment assign it in the localframe if it is not assigned there already. They also prepare the stackfor the assignment process. The stack invariant maintained by theassignment process is that the current right hand side value is on thetop, followed by the evaluated left hand side values and the originalright hand side value. Thus the start instruction leaves the righthand side value, the value of the left hand side variable, and againthe right hand side value on the top of the stack.The end instruction finds the final right hand side value followed bythe original right hand side value on the top of the stack. The finalvalue is removed and assigned to the appropriate variable binding.The original right hand side value is left on the top of the stack asthe value of the assignment expression.Evaluating a nested complex assignment involves evaluating a sequenceof expressions to obtain the left hand sides to modify, and thenevaluating a sequence of corresponding calls to replacement functionsin the opposite order. The function [[flattenPlace]] returns a listof the expressions that need to be considered, with [[*tmp*]] in placeof the current left hand side argument. For example, for an assignmentof the form [[f(g(h(x, k), j), i) <- v]] this produces\begin{verbatim}> flattenPlace(quote(f(g(h(x, k), j), i)))[[1]]f(`*tmp*`, i)[[2]]g(`*tmp*`, j)[[3]]h(`*tmp*`, k)\end{verbatim}The sequence of left hand side values needed consists of the originalvariable value, which is already on the stack, and the values of[[h(`*tmp*`, k)]] and [[g(`*tmp*`, j)]].In general the additional evaluations needed are of all but the firstexpression produced by [[flattenPlace]], evaluated in reverseorder. An argument context is used since there are already values onthe stack.<<compile code to compute left hand side values>>=ncntxt <- make.argContext(cntxt)flatPlace <- flattenPlace(lhs)for (p in rev(flatPlace[-1]))cmpGetterCall(p, cb, ncntxt)@ %defThe compilation of the individual calls carried out by[[cmpGetterCall]], which is presented in Section \ref{subsec:getter}.Each compilation places the new left hand side value on the top of thestack and then switches it with the value below, which is the originalright hand side value, to preserve the stack invariant.The function [[flattenPlace]] is defined as<<[[flattenPlace]] function>>=flattenPlace <- function(place) {places <- NULLwhile (typeof(place) == "language") {if (length(place) < 2)stop("bad assignment 1")tplace <- placetplace[[2]] <- as.name("*tmp*")places <- c(places, list(tplace))place <- place[[2]]}if (typeof(place) != "symbol")stop("bad assignment 2")places}@ %def flattenPlaceAfter the right hand side values have been computed the stack containsthe original right hand side value followed by the left hand sidevalues in the order in which they need to be modified. Code to callthe sequence of replacement functions is generated by<<compile code to compute right hand side values>>=cmpSetterCall(flatPlace[[1]], value, cb, ncntxt)for (p in flatPlace[-1])cmpSetterCall(p, as.name("*vtmp*"), cb, ncntxt)@ %defThe first call uses the expression for the original right hand side inits call; all othere will use [[*vtmp*]]. Each replacement functioncall compiled by [[cmpSetterCall]] will remove the top two elementsfrom the stack and then push the new right hand side value on thestack. [[cmpSetterCall]] is described in Section \ref{subsec:setter}.\subsection{Compiling setter calls}\label{subsec:setter}Setter calls, or calls to replacement functions, in compiledassignment expressions find stack that contains the current right handside value on the top followed by the current left hand side value.Some replacement function calls, such as calls to [[$<-]], are handledby an inlining mechanism described below. The general case when thefunction is specified by a symbol is handled a [[GETFUN]] instructionto push the function on the stack, pushing any additional arguments onthe stack, and using the [[SETTER_CALL]] instruction to execute thecall. This instruction adjusts the argument list by inserting as thefirst argument an evaluated promise for the left hand side value andas the last argument an evaluated promise for the right hand sidevalue; the final argument also has the [[value]] tag. The case wherethe function is specified in the form [[foo::bar]] or [[foo:::bar]]differs only compiling the function expression and using [[CHECKFUN]]to verify the result and prepare the stack.<<[[cmpSetterCall]] function>>=cmpSetterCall <- function(place, vexpr, cb, cntxt) {afun <- getAssignFun(place[[1]])acall <- as.call(c(afun, as.list(place[-1]), list(value = vexpr)))acall[[2]] <- as.name("*tmp*")ncntxt <- make.callContext(cntxt, acall)if (is.null(afun))## **** warn instead and arrange for cmpSpecial?## **** or generate code to signal runtime error?cntxt$stop(gettext("invalid function in complex assignment"))else if (typeof(afun) == "symbol") {if (! trySetterInline(afun, place, acall, cb, ncntxt)) {ci <- cb$putconst(afun)cb$putcode(GETFUN.OP, ci)<<compile additional arguments and call to setter function>>}}else {cmp(afun, cb, ncntxt)cb$putcode(CHECKFUN.OP)<<compile additional arguments and call to setter function>>}}@ %def cmpSetterCallThe common code for compiling additional arguments and issuing the[[SETTER_CALL]] instruction is given by<<compile additional arguments and call to setter function>>=cb$putcode(PUSHNULLARG.OP)cmpCallArgs(place[-c(1, 2)], cb, ncntxt)cci <- cb$putconst(acall)cvi <- cb$putconst(vexpr)cb$putcode(SETTER_CALL.OP, cci, cvi)@ %defThe [[PUSHNULL]] instruction places [[NULL]] in the argument list as afirst argument to serve as a place holder; [[SETTER_CALL]] replacesthis with the evaluated promise for the current left hand side value.The replacement function corresponding to [[fun]] is computed by[[getAssignFun]]. If [[fun]] is a symbol then the assignment functionis the symbol followed by [[<-]]. The function [[fun]] can also be anexpression of the form [[foo::bar]], in which case the replacementfunction is the expression [[foo::`bar<-`]]. [[NULL]] is returned if[[fun]] does not fit into one of these two cases.<<[[getAssignFun]] function>>=getAssignFun <- function(fun) {if (typeof(fun) == "symbol")as.name(paste(fun, "<-", sep=""))else {## check for and handle foo::bar(x) <- y assignments hereif (typeof(fun) == "language" && length(fun) == 3 &&(as.character(fun[[1]]) %in% c("::", ":::")) &&typeof(fun[[2]]) == "symbol" && typeof(fun[[3]]) == "symbol") {afun <- funafun[[3]] <- as.name(paste(fun[[3]],"<-", sep=""))afun}else NULL}}@ %def getAssignFunTo produce more efficient code some replacement function calls can beinlined and use specialized instructions. The most important of theseare [[$<-]], [[[<-]], and [[[[<-]]. An inlining mechanism similar tothe one described in Section \ref{sec:inlining} is used for thispurpose. A separate mechanism is needed because of the fact that inthe present context two arguments, the left hand side and right handside values, are already on the stack.<<setter inlining mechanism>>=setterInlineHandlers <- new.env(hash = TRUE, parent = emptyenv())setSetterInlineHandler <- function(name, h, package = "base") {if (exists(name, setterInlineHandlers, inherits = FALSE)) {entry <- get(name, setterInlineHandlers)if (entry$package != package) {fmt <- "handler for '%s' is already defined for another package"stop(gettextf(fmt, name), domain = NA)}}entry <- list(handler = h, package = package)assign(name, entry, setterInlineHandlers)}getSetterInlineHandler <- function(name, package = "base") {if (exists(name, setterInlineHandlers, inherits = FALSE)) {hinfo <- get(name, setterInlineHandlers)if (hinfo$package == package)hinfo$handlerelse NULL}else NULL}trySetterInline <- function(afun, place, call, cb, cntxt) {name <- as.character(afun)info <- getInlineInfo(name, cntxt)if (is.null(info))FALSEelse {h <- getSetterInlineHandler(name, info$package)if (! is.null(h))h(afun, place, call, cb, cntxt)else FALSE}}@ %defThe inline handler for [[$<-]] replacement calls uses the[[DOLLARGETS]] instruction. The handler declines to handle cases thatwould produce runtime errors; these are compiled by the genericmechanism.%% **** might be sueful to signal a warning at compile time<<setter inline handler for [[$<-]]>>=setSetterInlineHandler("$<-", function(afun, place, call, cb, cntxt) {if (any.dots(place) || length(place) != 3)FALSEelse {sym <- place[[3]]if (is.character(sym))sym <- as.name(sym)if (is.name(sym)) {ci <- cb$putconst(call)csi <- cb$putconst(sym)cb$putcode(DOLLARGETS.OP, ci, csi)TRUE}else FALSE}})@ %defThe replacement functions [[[<-]] and [[[[<-]]] are implemented as[[SPECIAL]] functions that do internal dispatching. They aretherefore compiled along the same lines as their correspondingaccessor functions as described in Section \ref{subsec:subset}. Thecommon pattern is implemented by [[cmpSetterDispatch]].<<[[cmpSetterDispatch]] function>>=cmpSetterDispatch <- function(start.op, dflt.op, afun, place, call, cb, cntxt) {if (any.dots(place))FALSE ## puntelse {ci <- cb$putconst(call)end.label <- cb$makelabel()cb$putcode(start.op, ci, end.label)if (length(place) > 2) {args <- place[-(1:2)]cmpBuiltinArgs(args, names(args), cb, cntxt, TRUE)}cb$putcode(dflt.op)cb$putlabel(end.label)TRUE}}@ %def cmpSetterDispatchThe two inlining handlers are then defined as<<setter inline handlers for [[ [<- ]] and [[ [[<- ]]>>=setSetterInlineHandler("[<-", function(afun, place, call, cb, cntxt)cmpSetterDispatch(STARTSUBASSIGN.OP, DFLTSUBASSIGN.OP,afun, place, call, cb, cntxt))setSetterInlineHandler("[[<-", function(afun, place, call, cb, cntxt)cmpSetterDispatch(STARTSUBASSIGN2.OP, DFLTSUBASSIGN2.OP,afun, place, call, cb, cntxt))@ %defAn inline handler is defined for [[@<-]] in order to suppress spuriouswarnings about the slot name symbol. A call in which the slot isspecified by a symbol is converted to one using a string instead, andis then compiled by a recursive call to [[cmpSetterCall]]; the handlerwill decline in this second call and the default compilation strategywill be used.<<setter inlining handler for [[@<-]]>>=setSetterInlineHandler("@<-", function(afun, place, acall, cb, cntxt) {if (! dots.or.missing(place) && length(place) == 3 &&typeof(place[[3]]) == "symbol") {place[[3]] <- as.character(place[[3]])vexpr <- acall[[length(acall)]]cmpSetterCall(place, vexpr, cb, cntxt)TRUE}else FALSE}, "methods")@\subsection{Compiling getter calls}\label{subsec:getter}Getter calls within an assignment also need special handling becauseof the left hand side argument being on the stack already and becauseof the need to restore the stack invariant. There are again two casesfor installing the getter function on the stack. These are thenfollowed by common code for handling the additional arguments and thecall.<<[[cmpGetterCall]] function>>=cmpGetterCall <- function(place, cb, cntxt) {ncntxt <- make.callContext(cntxt, place)fun <- place[[1]]if (typeof(fun) == "symbol") {if (! tryGetterInline(place, cb, ncntxt)) {ci <- cb$putconst(fun)cb$putcode(GETFUN.OP, ci)<<compile additional arguments and call to getter function>>}}else {cmp(fun, cb, ncntxt)cb$putcode(CHECKFUN.OP)<<compile additional arguments and call to getter function>>}}@ %def cmpGetterCallIn the common code, as in setter calls a [[NULL]] is placed on theargument stack as a place holder for the left hand side promise. Thenthe additional arguments are placed on the stack and the[[GETTER-CALL]] instruction is issued. This instruction installs theevaluated promise with the left hand side value as the first argumentand executes the call. The call will leave the next right left handside on the top of the stack. A [[SWAP]] instruction then switchesthe top two stack entries. This leaves the original right hand sidevalue on top followed by the new left hand side value returned by thegetter call and any other left hand side values produced by earliergetter call.<<compile additional arguments and call to getter function>>=cb$putcode(PUSHNULLARG.OP)cmpCallArgs(place[-c(1, 2)], cb, ncntxt)cci <- cb$putconst(place)cb$putcode(GETTER_CALL.OP, cci)cb$putcode(SWAP.OP)@ %defAgain an inlining mechnism is needed to handle calls to functions like[[$]] and [[[]]. These are able to use the same instructions as theinline handlers in Section \ref{subsec:subset} for ordinary calls to[[$]] and [[[]] but require some additional work to deal withmaintaining the stack invariant.The inlining mechanism itself is anologous tothe general one and theone for inlining setter calls.<<getter inlining mechanism>>=getterInlineHandlers <- new.env(hash = TRUE, parent = emptyenv())setGetterInlineHandler <- function(name, h, package = "base") {if (exists(name, getterInlineHandlers, inherits = FALSE)) {entry <- get(name, getterInlineHandlers)if (entry$package != package) {fmt <- "handler for '%s' is already defined for another package"stop(gettextf(fmt, name), domain = NA)}}entry <- list(handler = h, package = package)assign(name, entry, getterInlineHandlers)}getGetterInlineHandler <- function(name, package = "base") {if (exists(name, getterInlineHandlers, inherits = FALSE)) {hinfo <- get(name, getterInlineHandlers)if (hinfo$package == package)hinfo$handlerelse NULL}else NULL}tryGetterInline <- function(call, cb, cntxt) {name <- as.character(call[[1]])info <- getInlineInfo(name, cntxt)if (is.null(info))FALSEelse {h <- getGetterInlineHandler(name, info$package)if (! is.null(h))h(call, cb, cntxt)else FALSE}}@ %defThe inline handler for [[$]] in a getter context uses the [[DUP2ND]]instruction to push the second value on the stack, the previous lefthand side value, onto the stack. The [[DOLLAR]] instruction pops thisvalue, computes the component for this value and the symbol in theconstant pool, and pushes the result on the stack. A [[SWAP]]instruction then interchanges this value with the next value, which isthe original right hand side value, thus restoring the stackinvariant.<<getter inline handler for [[$]]>>=setGetterInlineHandler("$", function(call, cb, cntxt) {if (any.dots(call) || length(call) != 3)FALSEelse {sym <- call[[3]]if (is.character(sym))sym <- as.name(sym)if (is.name(sym)) {ci <- cb$putconst(call)csi <- cb$putconst(sym)cb$putcode(DUP2ND.OP)cb$putcode(DOLLAR.OP, ci, csi)cb$putcode(SWAP.OP)TRUE}else FALSE}})@ %defCalls to [[[]] and [[[[]] again need two instructions to support theinternal dispatch. The general pattern is implemented in[[cmpGetterDispatch]]. A [[DUP2ND]] instruction is used to place thefirst argument for the call on top of the stack, code analogous to thecode for ordinary calls to [[[]] and [[[[]] is used to make the call,and this is followed by a [[SWAP]] instruction to rearrange the stack.<<[[cmpGetterDispatch]] function>>=cmpGetterDispatch <- function(start.op, dflt.op, call, cb, cntxt) {if (any.dots(call))FALSE ## puntelse {ci <- cb$putconst(call)end.label <- cb$makelabel()cb$putcode(DUP2ND.OP)cb$putcode(start.op, ci, end.label)if (length(call) > 2) {args <- call[-(1:2)]cmpBuiltinArgs(args, names(args), cb, cntxt, TRUE)}cb$putcode(dflt.op)cb$putlabel(end.label)cb$putcode(SWAP.OP)TRUE}}@ %def cmpGetterDispatchThe two inline handlers are then defined as<<getter inline handlers for [[[]] and [[[[]]>>=setGetterInlineHandler("[", function(call, cb, cntxt)cmpGetterDispatch(STARTSUBSET.OP, DFLTSUBSET.OP, call, cb, cntxt))setGetterInlineHandler("[[", function(call, cb, cntxt)cmpGetterDispatch(STARTSUBSET2.OP, DFLTSUBSET2.OP, call, cb, cntxt))@ %def\section{Constant folding}A very valuable compiler optimization is constant folding. Forexample, an expression for computing a normal density function mayinclude the code\begin{verbatim}1 / sqrt(2 * pi)\end{verbatim}The interpreter would have to evaluate this expression each time it isneeded, but a compiler can often compute the value once at compiletime.The constant folding optimization can be applied at various points inthe compilation process: It can be applied to the source code beforecode generation or to the generated code in a separate optimizationphase. For now, constant folding is applied during the codegeneration phase.The [[constantFold]] function examines its expression argument andhandles each expression type by calling an appropriate function.<<[[constantFold]] function>>=## **** rewrite using switch??constantFold <- function(e, cntxt) {type = typeof(e)if (type == "language")constantFoldCall(e, cntxt)else if (type == "symbol")constantFoldSym(e, cntxt)else if (type == "promise")cntxt$stop(gettext("cannot constant fold literal promises"),cntxt)else if (type == "bytecode")cntxt$stop(gettext("cannot constant fold literal bytecode objects"),cntxt)else checkConst(e)}@ %def constantFold%% **** warn and return NULL instead of calling stop??The [[checkConst]] function decides whether a value is a constant thatis small enough and simple enough to enter into the constant pool. Ifso, then [[checkConst]] wraps the value in a list as the [[value]]component. If not, then [[NULL]] is returned.<<[[checkConst]] function>>=checkConst <- function(e) {if (mode(e) %in% constModes && length(e) <= maxConstSize)list(value = e)elseNULL}@ %def checkConstThe maximal size and acceptable modes are defined by<<[[maxConstSize]] and [[constModes]] definitions>>=maxConstSize <- 10constModes <- c("numeric", "logical", "NULL", "complex", "character")@ %def maxConstSize constModesFor now, constant folding is only applied for a particular set ofvariables and functions defined in the base package. The constantfolding code uses [[isBaseVar]] to determine whether a variable can beassumed to referencees the corresponding base variable given thecurrent compilation environment and optimization setting.[[constantFoldSym]] is applied to base variables in the [[constNames]]list.<<[[constantFoldSym]] function>>=## Assumes all constants will be defined in base.## Eventually allow other packages to define constants.## Any variable with locked binding could be used if type is right.## Allow local declaration of optimize, notinline declaration.constantFoldSym <- function(var, cntxt) {var <- as.character(var)if (var %in% constNames && isBaseVar(var, cntxt))checkConst(get(var, .BaseNamespaceEnv))else NULL}@ %def constantFoldSym<<[[constNames]] definition>>=constNames <- c("pi", "T", "F")@ %def constNamesCall expressions are handled by determining whether the functioncalled is eligible for constant folding, attempting to constant foldthe arguments, and calling the folding function. The result is thepassed to [[checkConst]]. If an error occurs in the call to thefolding function then [[constantFoldCall]] returns [[NULL]].<<[[constantFoldCall]] function>>=constantFoldCall <- function(e, cntxt) {fun <- e[[1]]if (typeof(fun) == "symbol") {ffun <- getFoldFun(fun, cntxt)if (! is.null(ffun)) {args <- as.list(e[-1])for (i in seq_along(args)) {a <- args[[i]]if (missing(a))return(NULL)val <- constantFold(a, cntxt)if (! is.null(val))args[i] <- list(val$value) ## **** in case value is NULLelse return(NULL)}modes <- unlist(lapply(args, mode))if (all(modes %in% constModes)) {tryCatch(checkConst(do.call(ffun, args)),error = function(e) NULL) ## **** issue warning??}else NULL}else NULL}else NULL}@ %def constantFoldCall%% **** separate out and explain argument processing chunk (maybe also the call)The functions in the base package eligible for constant folding are<<[[foldFuns]] definition>>=foldFuns <- c("+", "-", "*", "/", "^", "(",">", ">=", "==", "!=", "<", "<=", "||", "&&", "!","|", "&", "%%","c", "rep", ":","abs", "acos", "acosh", "asin", "asinh", "atan", "atan2","atanh", "ceiling", "choose", "cos", "cosh", "exp", "expm1","floor", "gamma", "lbeta", "lchoose", "lgamma", "log", "log10","log1p", "log2", "max", "min", "prod", "range", "round","seq_along", "seq.int", "seq_len", "sign", "signif","sin", "sinh", "sqrt", "sum", "tan", "tanh", "trunc","baseenv", "emptyenv", "globalenv","Arg", "Conj", "Im", "Mod", "Re","is.R")@ %def foldFuns[[getFoldFun]] checks the called function against this list andwhether the binding for the variable can be assumed to be from thebase package. If then returns the appropriate function from the basepackage or [[NULL]].<<[[getFoldFun]] function>>=## For now assume all foldable functions are in basegetFoldFun <- function(var, cntxt) {var <- as.character(var)if (var %in% foldFuns && isBaseVar(var, cntxt)) {val <- get(var, .BaseNamespaceEnv)if (is.function(val))valelseNULL}else NULL}@ %def getFoldFun\section{More top level functions}\subsection{Compiling closures}The function [[cmpfun]] is for compiling a closure. The body iscompiled with [[genCode]] and combined with the closure's formals andenvironment to form a compiled closure. The [[.Internal]] function[[bcClose]] does this. Some additional fiddling is needed if theclosure is an S4 generic. The need for the [[asS4]] bit seems a bitodd but it is apparently needed at this point.<<[[cmpfun]] function>>=cmpfun <- function(f, options = NULL) {type <- typeof(f)if (type == "closure") {cntxt <- make.toplevelContext(makeCenv(environment(f)), options)ncntxt <- make.functionContext(cntxt, formals(f), body(f))b <- genCode(body(f), ncntxt)val <- .Internal(bcClose(formals(f), b, environment(f)))attrs <- attributes(f)if (! is.null(attrs))attributes(val) <- attrsif (isS4(f)) ## **** should this really be needed??val <- asS4(val)val}else if (typeof(f) == "builtin" || type == "special")felse stop("cannot compile a non-function")}@ %def cmpfun\subsection{Compiling and loading files}A file can be compiled with [[cmpfile]] and loaded with [[loadcmp]].[[cmpfile]] reads in the expressions, compiles them, and serializesthe list of compiled expressions by calling the [[.Internal]] function[[save.to.file]].<<[[cmpfile]] function>>=cmpfile <- function(infile, outfile, ascii = FALSE, env = .GlobalEnv,verbose = FALSE, options = NULL) {if (! is.environment(env) || ! identical(env, topenv(env)))stop("'env' must be a top level environment")<<create [[outfile]] if argument is missing>><<check that [[infile]] and [[outfile]] are not the same>>forms <- parse(infile)nforms <- length(forms)if (nforms > 0) {expr.needed <- 1000expr.old <- options()$expressionsif (expr.old < expr.needed) {options(expressions = expr.needed)on.exit(options(expressions = expr.old))}cforms <- vector("list", nforms)cenv <- makeCenv(env)cntxt <- make.toplevelContext(cenv, options)cntxt$env <- addCenvVars(cenv, findLocalsList(forms, cntxt))for (i in 1:nforms) {e <- forms[[i]]if (verbose) {if (typeof(e) == "language" && e[[1]] == "<-" &&typeof(e[[3]]) == "language" && e[[3]][[1]] == "function")cat(paste("compiling function \"", e[[2]], "\"\n", sep=""))elsecat(paste("compiling expression", deparse(e, 20)[1],"...\n"))}cforms[[i]] <- genCode(e, cntxt)}cat(gettextf("saving to file \"%s\" ... ", outfile)).Internal(save.to.file(cforms, outfile, ascii))cat(gettext("done"), "\n", sep = "")}else warning("empty input file; no output written");invisible(NULL)}@ %def cmpfileThe default output file name is the base name of the input file with a[[.Rc]] extension.<<create [[outfile]] if argument is missing>>=if (missing(outfile)) {basename <- sub("\\.[a-zA-Z0-9]$", "", infile)outfile <- paste(basename, ".Rc", sep="")}@ %defAs a precaution it is useful to check that [[infile]] and [[outfile]]are not the same and signal an error if they are.<<check that [[infile]] and [[outfile]] are not the same>>=if (infile == outfile)stop("input and output file names are the same")@ %defThe [[loadcmp]] reads in the serialized list of expressionsusing the [[.Internal]] function [[load.from.file]]. The compiledexpressions are then evaluated in the global environment.<<[[loadcmp]] function>>=loadcmp <- function (file, envir = .GlobalEnv, chdir = FALSE) {if (!(is.character(file) && file.exists(file)))stop(gettextf("file '%s' does not exist", file), domain = NA)exprs <- .Internal(load.from.file(file))if (length(exprs) == 0)return(invisible())if (chdir && (path <- dirname(file)) != ".") {owd <- getwd()on.exit(setwd(owd), add = TRUE)setwd(path)}for (i in exprs) {yy <- eval(i, envir)}invisible()}@ %def loadcmp[[loadcmp]] is the analog to [[source]] for compiled files.Two additional functions that are currently not exported or used are[[cmpframe]] and [[cmplib]]. They should probably be removed.<<[[cmpframe]] function>>=cmpframe <- function(inpos, file) {expr.needed <- 1000expr.old <- options()$expressionsif (expr.old < expr.needed)options(expressions = expr.needed)on.exit(options(expressions = expr.old))attach(NULL, name="<compiled>")inpos <- inpos + 1outpos <- 2on.exit(detach(pos=outpos), add=TRUE)for (f in ls(pos=inpos,all=TRUE)) {def <- get(f, pos=inpos)if (typeof(def) == "closure") {cat(gettextf("compiling '%s'", f), "\n", sep = "")fc <- cmpfun(def)assign(f, fc, pos=outpos)}}cat(gettextf("saving to file \"%s\" ... ", file))save(list=ls(pos=outpos,all=T), file=file)cat(gettext("done"), "\n", sep = "")}@ %def cmpframe<<[[cmplib]] function>>=cmplib <- function(package, file) {package <- as.character(substitute(package))pkgname <- paste("package", package, sep = ":")pos <- match(pkgname, search());if (missing(file))file <- paste(package,".Rc",sep="")if (is.na(pos)) {library(package, char=TRUE)pos <- match(pkgname, search());on.exit(detach(pos=match(pkgname, search())))}cmpframe(pos, file)}@ %def cmplib\subsection{Enabling implicit compilation}<<[[enableJIT]] function>>=enableJIT <- function(level).Internal(enableJIT(level))@ %def enableJIT<<[[compilePKGS]] function>>=compilePKGS <- function(enable).Internal(compilePKGS(enable))@ %def compilePKGS\subsection{Setting compiler options}The [[setCompilerOptions]] function provides a means for users toadjust the default compiler option values. This interface isexperimental and may change.<<[[setCompilerOptions]] function>>=setCompilerOptions <- function(...) {options <- list(...)nm <- names(options)for (n in nm)if (! exists(n, compilerOptions))stop(gettextf("'%s' is not a valid compiler option", n),domain = NA)old <- list()for (n in nm) {op <- options[[n]]switch(n,optimize = {op <- as.integer(op)if (length(op) == 1 && 0 <= op && op <= 3) {old <- c(old, list(optimize =compilerOptions$optimize))compilerOptions$optimize <- op}},suppressAll = {if (identical(op, TRUE) || identical(op, FALSE)) {old <- c(old, list(suppressAll =compilerOptions$suppressAll))compilerOptions$suppressAll <- op}},suppressUndefined = {if (identical(op, TRUE) || identical(op, FALSE) ||is.character(op)) {old <- c(old, list(suppressUndefined =compilerOptions$suppressUndefined))compilerOptions$suppressUndefined <- op}})}old}@ %defFor now, a [[.onLoad]] function is used to allow all warning to besuppressed. This is probably useful for building packages, since theway lazy loading is done means variables defined in shared librariesare not available and produce a raft of warnings.<<[[.onLoad]] function>>=.onLoad <- function(libname, pkgname) {if (Sys.getenv("R_COMPILER_SUPPRESS_ALL") != "")setCompilerOptions(suppressAll = TRUE)}@ %def .onLoad\subsection{Disassembler}A minimal disassembler is provided by [[disassemble]]. This isprimarily useful for debugging the compiler. A more readable outputrepresentation might be nice to have. It would also probably makesense to give the result a class and write a print method.<<[[disassemble]] function>>=disassemble <- function(code) {.CodeSym <- as.name(".Code")disasm.const<-function(x)if (typeof(x)=="list" && length(x) > 0 && identical(x[[1]], .CodeSym))disasm(x) else xdisasm <-function(code) {code[[2]]<-bcDecode(code[[2]])code[[3]]<-lapply(code[[3]], disasm.const)code}if (typeof(code)=="closure") {code <- .Internal(bodyCode(code))if (typeof(code) != "bytecode")stop("function is not compiled")}dput(disasm(.Internal(disassemble(code))))}@ %def disassembleThe [[.Internal]] function [[disassssemble]] extracts the numeric bodevector and constant pool. The function [[bcDecode]] uses the[[Opcodes.names]] array to translate the numeric opcodes into symbolicones. At this point not enough information is available in areasonable place to also convert labels back to symbolic form.<<[[bcDecode]] function>>=bcDecode <- function(code) {n <- length(code)ncode <- vector("list", n)ncode[[1]] <- code[1] # version numberi <- 2while (i <= n) {name<-Opcodes.names[code[i]+1]argc<-Opcodes.argc[[code[i]+1]]ncode[[i]] <- as.name(name)i<-i+1if (argc > 0)for (j in 1:argc) {ncode[[i]]<-code[i]i<-i+1}}ncode}@ %def bcDecode\section{Discussion and future directions}Despite its long gestation period this compiler should be viewed as afirst pass at creating a byte code compiler for R. The compileritself is very simple in design as a single pass compiler with noseparate optimization phases. Similarly the virtual machine uses avery simple stack design. While the compiler already achieves someuseful performance improvements on loop-intensive code, more can beachieved with more sophisticated approaches. This will be explored infuture work.A major objective of this first version was to reproduce R'sinterpreted semantics with as few departures as possible while at thesame time optimizing a number of aspect of the execution process. Theinlining rules controlled by an optimization level setting seem toprovide a good way of doing this, and the default optimization settingseems to be reasonably effective. Mechanisms for adjusting thedefault settings via declarations will be explored and added in thenear future.Future versions of the compiler and the engine will explore a numberof alternative designs. Switching to a register-based virtual machinewill be explored fairly soon. Preliminary experiments suggest thatthis can provide significant improvements in the case of tight loopsby allowing allocation of intermediate results to be avoided in manycases. It may be possible at least initially to keep the currentcompiler ant just translate the stack-based machine code to aregister-based code.Another direction that will be explored is whether sequences ofarithmetic and other numerical operations can be fused and possiblyvectorized. Again preliminary experiments are promising, but moreexploration is needed.Other improvements to be examined may affect interpreted code as muchas compiled code. These include more efficient environmentrepresentations and more efficient calling conventions.%% **** add some benchmarks%% **** comment on engine%% **** lots of other builtins, specials, and .Internals%% **** controlling compiler warnings%% **** merging in codetools features%% **** put in install-time tests of assumptions about BUILTINs, etc.%% **** switch to register-based engine%% **** make function calls more efficient%% **** try to stay within a single bceval call%% **** think about optimizing things like mean?%% **** jit that compiles all expressions??%% **** can it record code for expr/env pairs or some such?%% **** can it inline primitives at that point?%% **** Stuff to think about:%% **** alternate environment representation for compiler%% **** optimizing function calls in general%% **** avoiding matching for BOA calls%% **** Think about different ways of handling environments. Should every op%% **** return a new env object that includes (possible) new local vars?%% **** Useful to be able to distinguish ... in args from assigned-to ...%% **** matrix subsetting has to be slow because of the way dim is stored.%% **** might make sense to explicitly compile as multiple operations and%% **** invariant hoisting out of loops for loops?%% **** look at tail call optimization -- pass call and parent.frame??%% **** eliminating variables not needed?%% **** alternate builtin call implementations?%% **** install compiled promises in code body??%% **** catch errors at dispatching of inliners; fall back to runtime error\appendix\section{General utility functions}This appendix provides a few general utility functions.The utility function [[pasteExpr]] is used in the error messages.%% **** use elipsis instead of collapse??%% **** use error context or catch errors?%% **** maybe don't need expression if we catch errors?<<[[pasteExpr]] function>>=pasteExpr <- function(e, prefix = "\n ") {de <- deparse(e)if (length(de) == 1) sQuote(de)else paste(prefix, deparse(e), collapse="")}@ %def pasteExprThe function [[dots.or.missing]] checks the argument list for anymissing or [[...]] arguments:<<[[dots.or.missing]] function>>=dots.or.missing <- function(args) {for (i in 1:length(args)) {a <-args[[i]]if (missing(a)) return(TRUE) #**** better test?if (typeof(a) == "symbol" && a == "...") return(TRUE)}return(FALSE)}@ %def dots.or.missingThe function [[any.dots]] is defined as<<[[any.dots]] function>>=any.dots <- function(args) {for (i in 1:length(args)) {a <-args[[i]]if (! missing(a) && typeof(a) == "symbol" && a == "...")return(TRUE)}return(FALSE)}@ %def any.dotsThe utility function [[is.ddsym]] is used to recognize symbols of theform [[..1]], [[..2]], and so on.<<[[is.ddsym]] function>>=is.ddsym <- function(name) {(is.symbol(name) || is.character(name)) &&length(grep("^\\.\\.[0-9]+$", as.character(name))) != 0}@ %def is.ddsym[[missingArgs]] takes an argument list for a call a logical vectorindicating for each argument whether it is empty (missing) or not.<<[[missingArgs]] function>>=missingArgs <- function(args) {val <- logical(length(args))for (i in seq_along(args)) {a <- args[[i]]if (missing(a))val[i] <- TRUEelseval[i] <- FALSE}val}@ %def missingArgsFor compiling packages during the lazy loading process [[cmpfun]]needs to call [[asS4]], which is defined in [[base]] but calls[[methods::as]]. When compiling [[methods]] it turns out that[[methods]] is not fully initialized at the point where this is neededand therefore the exported [[as]] function is not available. For now,the [[compiler]] package defines its own [[asS4]] that uses theunexported function [[methods:::as]] instead. It would probably bebetter to make this change in [[base::asS4]] instead.%% **** change this in base instead??<<[[asS4]] function>>=## We need our own version of base::asS4 that differs only from the one## in base by using methods:::as instead of methods::as. This is needed## to JIT compile methods as the lazy loading mechanism used there does## it's thing during the first namespace load at a point where exports## are not yet set up.asS4 <- function(object, flag = TRUE, complete = TRUE) {flag <- methods:::as(flag, "logical")if(length(flag) != 1L || is.na(flag))stop("expected a single logical value for the S4 state flag").Call("R_setS4Object", object, flag, complete, PACKAGE = "base")}@ %def asS4\section{Environment utilities}This appendix presents some utilities for computations on environments.The function [[frameTypes]] takes an environment argument and returnsa character vector with elements for each frame in the environmentclassifying the frame as local, namespace, or global. The environmentis assumed to be a standard evaluation environment that contains[[.GlobalEnv]] as one of its parents. It does this by computing thenumber of local, namespace, and global frames and then generating theresult using [[rep]].<<[[frameTypes]] function>>=frameTypes <- function(env) {top <- topenv(env)empty <- emptyenv()<<find the number [[nl]] of local frames>><<find the number [[nn]] of namespace frames>><<find the number [[ng]] of global frames>>rep(c("local", "namespace", "global"), c(nl, nn, ng))}@ %def frameTypesThe number of local frames is computes by marching down the parentframes with [[parent.env]] until the top level environment is reached.<<find the number [[nl]] of local frames>>=nl <- 0while (! identical(env, top)) {env <- parent.env(env)nl <- nl + 1if (identical(env, empty))stop("not a proper evaluation environment")}@ %defThe number of namespace frames is computed by continuing down theparent frames until [[.GlobalEnv]] is reached.<<find the number [[nn]] of namespace frames>>=nn <- 0if (isNamespace(env)) {while (! identical(env, .GlobalEnv)) {env <- parent.env(env)nn <- nn + 1if (identical(env, empty))stop("not a proper evaluation environment")}}@ %defFinally the number of global frames is computed by continuing untilthe empty environment is reached. An alternative would be to computethe length of the result returned by [[search]]<<find the number [[ng]] of global frames>>=ng <- 0while (! identical(env, empty)) {env <- parent.env(env)ng <- ng + 1}@The function [[findHomeNS]] takes a variable name and a namespaceframe, or a namespace imports frame, and returns the namespace framein which the variable was originally defined, if any. The code assumesthat renaming has not been used (it may no longer be supported in thenamespace implementation in any case). Just in case, an attempt ismade to check for renaming. The result returned is the namaspaceframe for the namespace in which the variable was defined or [[NULL]]if the variable was not defined in the specified namespace or one ofits imports, or if the home namespace cannot be determined.<<[[findHomeNS]] function>>=## Given a symbol name and a namespace environment (or a namespace## imports environment) find the namespace in which the symbol's value## was originally defined. Returns NULL if the symbol is not found via## the namespace.findHomeNS <- function(sym, ns) {<<if [[ns]] is an imports frame find the corresponding namespace>>if (exists(sym, ns, inherits = FALSE))nselse if (exists(".__NAMESPACE__.", ns, inherits = FALSE)) {<<search the imports for [[sym]]>>NULL}else NULL}@ %def findHomeNSIf the [[ns]] argument is not a namespace frame it should be theimports frame of a namespace. Such an imports frame should have a[[name]] attribute or the form [["imports:foo"]] it it is associatedwith namespace [["foo"]]. This is used to find the namespace framethat owns the imports frame in this case, and this frames is thenassigned to [[ns]].<<if [[ns]] is an imports frame find the corresponding namespace>>=if (! isNamespace(ns)) {## As a convenience this allows for 'ns' to be the imports fame## of a namespace. It appears that these now have a 'name'## attribute of the form 'imports:foo' if 'foo' is the## namespace.name <- attr(ns, "name")if (is.null(name))stop("'ns' must be a namespace or a namespace imports environment")ns <- getNamespace(sub("imports:", "", attr(ns, "name")))}@ %defThe imports are searched in reverse order since in the case of nameconflicts the last one imported will take precedence. Full importsvia an [[import]] directive have to be handled differently thanselective imports created with [[importFrom]] directives.<<search the imports for [[sym]]>>=imports <- get(".__NAMESPACE__.", ns)$importsfor (i in rev(seq_along(imports))) {iname <- names(imports)[i]ins <- getNamespace(iname)if (identical(imports[[i]], TRUE)) {<<search in a full import>>}else {<<search in a selective import>>}}@ %defIf an entry in the [[imports]] specification for the import sourcenamespace [[ins]] has value [[TRUE]], then all exports of the [[ins]]have been imported. If [[sym]] is in the exports then the result of arecursive call to [[findHomeNS]] is returned.<<search in a full import>>=if (identical(ins, .BaseNamespaceEnv))exports <- .BaseNamespaceEnvelseexports <- get(".__NAMESPACE__.", ins)$exportsif (exists(sym, exports, inherits = FALSE))return(findHomeNS(sym, ins))@ %defFor selective imports the [[imports]] entry is a named charactervector mapping export name to import name. In the absence of renamingthe names should match the values; if this is not the case [[NULL]] isreturned. Otherwise, a match results again in returning a recursivecall to [[findHomeNS]].<<search in a selective import>>=exports <- imports[[i]]pos <- match(sym, names(exports), 0)if (pos) {## If renaming has been used things get too## confusing so return NULL. (It is not clear if## renaming this is still supported by the## namespace code.)if (sym == exports[pos])return(findHomeNS(sym, ins))elsereturn(NULL)}@Given a package package frame from the global environment the function[[packFrameName]] returns the associated package name, which iscomputed from the [[name]] attribute.%% **** might be good the check the name is of the form package:foo<<[[packFrameName]] function>>=packFrameName <- function(frame) {fname <- attr(frame, "name")if (is.character(fname))sub("package:", "", fname)else if (identical(frame , baseenv()))"base"else ""}@ %def packFrameNameFor a namespace frame the function [[nsName]] retrieves the namespacename from the namespace information structure.<<[[nsName]] function>>=nsName <- function(ns) {if (identical(ns, .BaseNamespaceEnv))"base"else {name <- ns$.__NAMESPACE__.$spec["name"]if (is.character(name))as.character(name) ## strip off nameselse ""}}@ %def nsName\section{Opcode constants}\subsection{Symbolic opcode names}<<opcode definitions>>=BCMISMATCH.OP <- 0RETURN.OP <- 1GOTO.OP <- 2BRIFNOT.OP <- 3POP.OP <- 4DUP.OP <- 5PRINTVALUE.OP <- 6STARTLOOPCNTXT.OP <- 7ENDLOOPCNTXT.OP <- 8DOLOOPNEXT.OP <- 9DOLOOPBREAK.OP <- 10STARTFOR.OP <- 11STEPFOR.OP <- 12ENDFOR.OP <- 13SETLOOPVAL.OP <- 14INVISIBLE.OP <- 15LDCONST.OP <- 16LDNULL.OP <- 17LDTRUE.OP <- 18LDFALSE.OP <- 19GETVAR.OP <- 20DDVAL.OP <- 21SETVAR.OP <- 22GETFUN.OP <- 23GETGLOBFUN.OP <- 24GETSYMFUN.OP <- 25GETBUILTIN.OP <- 26GETINTLBUILTIN.OP <- 27CHECKFUN.OP <- 28MAKEPROM.OP <- 29DOMISSING.OP <- 30SETTAG.OP <- 31DODOTS.OP <- 32PUSHARG.OP <- 33PUSHCONSTARG.OP <- 34PUSHNULLARG.OP <- 35PUSHTRUEARG.OP <- 36PUSHFALSEARG.OP <- 37CALL.OP <- 38CALLBUILTIN.OP <- 39CALLSPECIAL.OP <- 40MAKECLOSURE.OP <- 41UMINUS.OP <- 42UPLUS.OP <- 43ADD.OP <- 44SUB.OP <- 45MUL.OP <- 46DIV.OP <- 47EXPT.OP <- 48SQRT.OP <- 49EXP.OP <- 50EQ.OP <- 51NE.OP <- 52LT.OP <- 53LE.OP <- 54GE.OP <- 55GT.OP <- 56AND.OP <- 57OR.OP <- 58NOT.OP <- 59DOTSERR.OP <- 60STARTASSIGN.OP <- 61ENDASSIGN.OP <- 62STARTSUBSET.OP <- 63DFLTSUBSET.OP <- 64STARTSUBASSIGN.OP <- 65DFLTSUBASSIGN.OP <- 66STARTC.OP <- 67DFLTC.OP <- 68STARTSUBSET2.OP <- 69DFLTSUBSET2.OP <- 70STARTSUBASSIGN2.OP <- 71DFLTSUBASSIGN2.OP <- 72DOLLAR.OP <- 73DOLLARGETS.OP <- 74ISNULL.OP <- 75ISLOGICAL.OP <- 76ISINTEGER.OP <- 77ISDOUBLE.OP <- 78ISCOMPLEX.OP <- 79ISCHARACTER.OP <- 80ISSYMBOL.OP <- 81ISOBJECT.OP <- 82ISNUMERIC.OP <- 83NVECELT.OP <- 84NMATELT.OP <- 85SETNVECELT.OP <- 86SETNMATELT.OP <- 87AND1ST.OP <- 88AND2ND.OP <- 89OR1ST.OP <- 90OR2ND.OP <- 91GETVAR_MISSOK.OP <- 92DDVAL_MISSOK.OP <- 93VISIBLE.OP <- 94SETVAR2.OP <- 95STARTASSIGN2.OP <- 96ENDASSIGN2.OP <- 97SETTER_CALL.OP <- 98GETTER_CALL.OP <- 99SWAP.OP <- 100DUP2ND.OP <- 101SWITCH.OP <- 102RETURNJMP.OP <- 103@\subsection{Instruction argument counts and names}<<opcode argument counts>>=Opcodes.argc <- list(BCMISMATCH.OP = 0,RETURN.OP = 0,GOTO.OP = 1,BRIFNOT.OP = 2,POP.OP = 0,DUP.OP = 0,PRINTVALUE.OP = 0,STARTLOOPCNTXT.OP = 1,ENDLOOPCNTXT.OP = 0,DOLOOPNEXT.OP = 0,DOLOOPBREAK.OP = 0,STARTFOR.OP = 3,STEPFOR.OP = 1,ENDFOR.OP = 0,SETLOOPVAL.OP = 0,INVISIBLE.OP = 0,LDCONST.OP = 1,LDNULL.OP = 0,LDTRUE.OP = 0,LDFALSE.OP = 0,GETVAR.OP = 1,DDVAL.OP = 1,SETVAR.OP = 1,GETFUN.OP = 1,GETGLOBFUN.OP = 1,GETSYMFUN.OP = 1,GETBUILTIN.OP = 1,GETINTLBUILTIN.OP = 1,CHECKFUN.OP = 0,MAKEPROM.OP = 1,DOMISSING.OP = 0,SETTAG.OP = 1,DODOTS.OP = 0,PUSHARG.OP = 0,PUSHCONSTARG.OP = 1,PUSHNULLARG.OP = 0,PUSHTRUEARG.OP = 0,PUSHFALSEARG.OP = 0,CALL.OP = 1,CALLBUILTIN.OP = 1,CALLSPECIAL.OP = 1,MAKECLOSURE.OP = 1,UMINUS.OP = 1,UPLUS.OP = 1,ADD.OP = 1,SUB.OP = 1,MUL.OP = 1,DIV.OP = 1,EXPT.OP = 1,SQRT.OP = 1,EXP.OP = 1,EQ.OP = 1,NE.OP = 1,LT.OP = 1,LE.OP = 1,GE.OP = 1,GT.OP = 1,AND.OP = 1,OR.OP = 1,NOT.OP = 1,DOTSERR.OP = 0,STARTASSIGN.OP = 1,ENDASSIGN.OP = 1,STARTSUBSET.OP = 2,DFLTSUBSET.OP = 0,STARTSUBASSIGN.OP = 2,DFLTSUBASSIGN.OP = 0,STARTC.OP = 2,DFLTC.OP = 0,STARTSUBSET2.OP = 2,DFLTSUBSET2.OP = 0,STARTSUBASSIGN2.OP = 2,DFLTSUBASSIGN2.OP = 0,DOLLAR.OP = 2,DOLLARGETS.OP = 2,ISNULL.OP = 0,ISLOGICAL.OP = 0,ISINTEGER.OP = 0,ISDOUBLE.OP = 0,ISCOMPLEX.OP = 0,ISCHARACTER.OP = 0,ISSYMBOL.OP = 0,ISOBJECT.OP = 0,ISNUMERIC.OP = 0,NVECELT.OP = 0,NMATELT.OP = 0,SETNVECELT.OP = 0,SETNMATELT.OP = 0,AND1ST.OP = 2,AND2ND.OP = 1,OR1ST.OP = 2,OR2ND.OP = 1,GETVAR_MISSOK.OP = 1,DDVAL_MISSOK.OP = 1,VISIBLE.OP = 0,SETVAR2.OP = 1,STARTASSIGN2.OP = 1,ENDASSIGN2.OP = 1,SETTER_CALL.OP = 2,GETTER_CALL.OP = 1,SWAP.OP = 0,DUP2ND.OP = 0,SWITCH.OP = 4,RETURNJMP.OP = 0)@<<opcode namse>>=Opcodes.names <- names(Opcodes.argc)@ %def Opcodes.names\section{Implementation file}%% Benchmark code:% sf <- function(x) {% s <- 0% for (y in x)% s <- s + y% s% }% sfc <- cmpfun(sf)% x <- 1 : 1000000% system.time(sf(x))% system.time(sfc(x))%% **** need header/copyright/license stuff here<<cmp.R>>=# Automatically generated from ../noweb/compiler.nw.## File src/library/compiler/R/cmp.R# Part of the R package, http://www.R-project.org# Copyright (C) 2001-2011 Luke Tierney## This program is free software; you can redistribute it and/or modify# it under the terms of the GNU General Public License as published by# the Free Software Foundation; either version 2 of the License, or# (at your option) any later version.## This program is distributed in the hope that it will be useful,# but WITHOUT ANY WARRANTY; without even the implied warranty of# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the# GNU General Public License for more details.## A copy of the GNU General Public License is available at# http://www.r-project.org/Licenses/#### Compiler options##<<compiler options data base>><<[[getCompilerOption]] function>>#### General Utilities##<<[[pasteExpr]] function>><<[[dots.or.missing]] function>><<[[any.dots]] function>><<[[is.ddsym]] function>><<[[asS4]] function>><<[[missingArgs]] function>>#### Environment utilities##<<[[frameTypes]] function>><<[[findHomeNS]] function>><<[[packFrameName]] function>><<[[nsName]] function>>#### Finding possible local variables##<<[[getAssignedVar]] function>><<[[findLocals1]] function>><<[[findLocalsList1]] function>><<[[findLocals]] function>><<[[findLocalsList]] function>>#### Compilation environment implementation##<<[[makeCenv]] function>><<[[addCenvVars]] function>><<[[addCenvFrame]] function>><<[[findCenvVar]] function>><<[[isBaseVar]] function>><<[[funEnv]] function>><<[[findLocVar]] function>><<[[findFunDef]] function>><<[[findVar]] function>>#### Constant folding##<<[[maxConstSize]] and [[constModes]] definitions>><<[[constNames]] definition>><<[[checkConst]] function>><<[[constantFoldSym]] function>><<[[getFoldFun]] function>><<[[constantFoldCall]] function>><<[[constantFold]] function>><<[[foldFuns]] definition>><<[[languageFuns]] definition>>#### Opcode constants##<<opcode argument counts>><<opcode namse>><<opcode definitions>>#### Code buffer implementation##<<[[make.codeBuf]] function>><<[[codeBufCode]] function>><<[[genCode]] function>>#### Compiler contexts##<<[[make.toplevelContext]] function>><<[[make.callContext]] function>><<[[make.promiseContext]] function>><<[[make.functionContext]] function>><<[[make.nonTailCallContext]] function>><<[[make.argContext]] function>><<[[make.noValueContext]] function>><<[[make.loopContext]] function>>#### Compiler top level##<<[[cmp]] function>><<[[cmpConst]] function>><<[[cmpSym]] function>><<[[cmpCall]] function>><<[[cmpCallSymFun]] function>><<[[cmpCallExprFun]] function>><<[[cmpCallArgs]] function>><<[[cmpConstArg]]>><<[[checkCall]] function>>## **** need to handle ... and ..n arguments specially## **** separate call opcode for calls with named args?## **** for (a in e[[-1]]) ... goes into infinite loop<<[[cmpTag]] function>>#### Inlining mechanism##<<inline handler implementation>>## tryInline implements the rule permitting inlining as they stand now:## Inlining is controlled by the optimize compiler option, with possible## values 0, 1, 2, which mean#### optimize = 0 -- no inlining## optimize = 1 -- can inline syntactically special functions and## functions found via a namespace## optimize = 2 -- can inline any functions from case packages#### This can easily be modified to allow functions to do things like#### declare(optimize = 2)#### or#### declare(notinline = c("diag", "dim"))#### **** need to figure out if there is a sensible way to declare things at## **** the package level<<[[getInlineInfo]] function>><<[[tryInline]] function>>#### Inline handlers for some SPECIAL functions##<<inlining handler for [[function]]>><<inlining handler for left brace function>><<inlining handler for [[if]]>><<inlining handler for [[&&]]>><<inlining handler for [[||]]>>#### Inline handlers for assignment expressions##<<setter inlining mechanism>><<getter inlining mechanism>><<[[cmpAssign]] function>><<[[flattenPlace]] function>><<[[cmpGetterCall]] function>><<[[checkAssign]] function>><<[[cmpSymbolAssign]] function>><<[[cmpComplexAssign]] function>><<[[cmpSetterCall]] function>><<[[getAssignFun]] function>><<[[cmpSetterDispatch]] function>><<inlining handlers for [[<-]], [[=]], and [[<<-]]>><<setter inline handler for [[$<-]]>><<setter inline handlers for [[ [<- ]] and [[ [[<- ]]>><<[[cmpGetterDispatch]] function>><<getter inline handler for [[$]]>><<getter inline handlers for [[[]] and [[[[]]>>#### Inline handlers for loops##<<inlining handlers for [[next]] and [[break]]>><<[[isLoopStopFun]] function>><<[[isLoopTopFun]] function>><<[[checkSkipLoopCntxtList]] function>><<[[checkSkipLoopCntxt]] function>><<inlining handler for [[repeat]] loops>><<[[cmpRepeatBody]] function>><<inlining handler for [[while]] loops>><<[[cmpWhileBody]] function>><<inlining handler for [[for]] loops>><<[[cmpForBody]] function>>#### Inline handlers for one and two argument primitives##<<[[cmpPrim1]] function>><<[[cmpPrim2]] function>><<inline handlers for [[+]] and [[-]]>><<inline handlers for [[*]] and [[/]]>><<inline handlers for [[^]], [[exp]], and [[sqrt]]>><<inline handlers for comparison operators>><<inline handlers for [[&]] and [[|]]>><<inline handler for [[!]]>>#### Inline handlers for the left parenthesis function##<<inlining handler for [[(]]>>#### Inline handlers for general BUILTIN and SPECIAL functions##<<[[cmpBuiltin]] function>><<[[cmpBuiltinArgs]] function>><<[[cmpSpecial]] function>><<inlining handler for [[.Internal]]>>#### Inline handlers for subsetting and related operators##<<[[cmpDispatch]] function>><<inlining handlers for some dispatching SPECIAL functions>><<inlining handler for [[$]]>>#### Inline handler for local() and return() functions##<<inlining handler for [[local]] function>><<inlining handler for [[return]] function>>#### Inline handlers for the family of is.xyz primitives##<<[[cmpIs]] function>><<inlining handlers for [[is.xyz]] functions>>#### Default inline handlers for BUILTIN and SPECIAL functions##<<install default inlining handlers>>#### Inline handlers for some .Internal functions##<<[[simpleFormals]] function>><<[[simpleArgs]] function>><<[[is.simpleInternal]] function>><<[[inlineSimpleInternalCall]] function>><<[[cmpSimpleInternal]] function>><<inline safe simple [[.Internal]] functions from [[base]]>><<inline safe simple [[.Internal]] functions from [[stats]]>>#### Inline handler for switch##<<[[findActionIndex]] function>><<inline handler for [[switch]]>>#### Inline handlers to control warnings##<<[[cmpMultiColon]] function>><<inlining handlers for [[::]] and [[:::]]>><<setter inlining handler for [[@<-]]>><<inlining handler for [[with]]>><<inlining handler for [[require]]>>#### Compiler warnings##<<[[suppressAll]] function>><<[[suppressUndef]] function>><<[[notifyLocalFun]] function>><<[[notifyUndefFun]] function>><<[[notifyUndefVar]] function>><<[[notifyNoSuperAssignVar]] function>><<[[notifyWrongArgCount]] function>><<[[notifyWrongDotsUse]] function>><<[[notifyWrongBreakNext]] function>><<[[notifyBadCall]] function>><<[[notifyBadAssignFun]] function>><<[[notifyMultipleSwitchDefaults]] function>>#### Compiler interface##<<[[compile]] function>><<[[cmpfun]] function>><<[[cmpframe]] function>><<[[cmplib]] function>><<[[cmpfile]] function>><<[[loadcmp]] function>><<[[enableJIT]] function>><<[[compilePKGS]] function>><<[[setCompilerOptions]] function>><<[[.onLoad]] function>>#### Disassembler##<<[[bcDecode]] function>><<[[disassemble]] function>>@\end{document}