Index: codegen/build.tcl ================================================================== --- codegen/build.tcl +++ codegen/build.tcl @@ -4468,24 +4468,25 @@ # The arguments as an LLVM vector value reference. Note that # this includes the function name as the first argument. # havecf - # Tcl boolean indicating if we have a valid callframe. # cf - The reference to the current callframe if 'havecf' is true. + # ns - The official current namespace, as Namespace*. # ec - Location to write the Tcl return code into, as an LLVM int* # reference. # resultName (optional) - # A name to give to the result value. # # Results: # An LLVM value reference. - method invoke {arguments havecf cf ec {resultName ""}} { + method invoke {arguments havecf cf ns ec {resultName ""}} { my ExtractVector $arguments if {!$havecf} { set cf {} } - my call ${tcl.invoke.command} [list $len $ary $cf $ec] $resultName + my call ${tcl.invoke.command} [list $len $ary $cf $ns $ec] $resultName } # Builder:invokeExpanded -- # # Generate code to call a Tcl command while doing argument expansion. @@ -4494,21 +4495,22 @@ # Parameters: # arguments - # The arguments as an LLVM vector value reference. Note that # this includes the function name as the first argument. # flags - LLVM bit array indicating which arguments to expand. + # ns - The official current namespace, as Namespace*. # ec - Location to write the Tcl return code into, as an LLVM int* # reference. # resultName (optional) - # A name to give to the result value. # # Results: # An LLVM value reference. - method invokeExpanded {arguments flags ec {resultName ""}} { + method invokeExpanded {arguments flags ns ec {resultName ""}} { my ExtractVector $arguments - my call ${tcl.invoke.expanded} [list $len $ary $flags $ec] $resultName + my call ${tcl.invoke.expanded} [list $len $ary $flags $ns $ec] $resultName } # Builder:isBoolean(INT BOOLEAN) -- # # Test if a value is a boolean. Quadcode implementation ('isBoolean'). Index: codegen/compile.tcl ================================================================== --- codegen/compile.tcl +++ codegen/compile.tcl @@ -337,11 +337,11 @@ try { $b @location $currentline switch -exact -- [lindex $l 0 0] { "entry" { lassign [my IssueEntry $l] \ - theframe thevarmap syntheticargs + theframe thevarmap thens syntheticargs } "confluence" - "unset" { # Do nothing; required for SSA computations only } "@debug-line" { @@ -785,20 +785,20 @@ } set phiAnnotations {} } } "invoke" { - set arguments [my IssueInvoke $theframe $l] + set arguments [my IssueInvoke $theframe $l $thens] foreach aa $arguments { set arguments [lassign $arguments a] if {$a ni $arguments && consumed($a, $pc + 1)} { lappend consumed $a } } } "invokeExpanded" { - set arguments [my IssueInvokeExpanded $theframe $l] + set arguments [my IssueInvokeExpanded $theframe $l $thens] foreach aa $arguments { set arguments [lassign $arguments a] if {$a ni $arguments && consumed($a, $pc + 1)} { lappend consumed $a } @@ -1163,11 +1163,13 @@ method IssueEntry {quad} { lassign $quad opcode tgt vars # When no frame is wanted if {$tgt eq {}} { - return [list [$b null CALLFRAME] {} {}] + # FIXME: get namespace + set ns {} + return [list [$b null CALLFRAME] {} $ns {}] } # Store the fact that we must generate complex metadata for this # function/command, and the variable where this metadata will be # stored. @@ -1226,11 +1228,12 @@ lassign [$b frame.create $varmeta $argc $argv \ [$b load $procmeta "proc.metadata"] \ [$b load $localcache "proc.localcache"]] \ theframe thevarmap my StoreResult $tgt $theframe - return [list $theframe $thevarmap $drop] + set thens [$b dereference $theframe 0 CallFrame.nsPtr] + return [list $theframe $thevarmap $thens $drop] } # TclCompiler:IssueInvoke -- # # Generate the code for invoking another Tcl command. Must only be @@ -1239,48 +1242,46 @@ # Parameters: # callframe - # The callframe. # operation - # The quadcode descriptor for the instruction. + # thens - The current namespace, used for command resolution. # # Results: # The set of arguments that might have been consumed in the operation # (for cleanup by the caller of this method). - method IssueInvoke {callframe operation} { + method IssueInvoke {callframe operation thens} { set arguments [lassign $operation opcode tgt thecallframe origname] set vname [my LocalVarName $tgt] set BASETYPES {ZEROONE INT DOUBLE NUMERIC STRING} - set resolved {} # Is this a literal name for a function we already know the signature # of? If so, we can use a direct call. To work this out, we need to # resolve the command within the namespace context of the procedure. if {literal($origname)} { # Resolve the name. set name [my FuncName [lindex $origname 1]] - set fullname [my GenerateFunctionName $name arguments $arguments] + set fullname [my GenerateFunctionName $name arguments \ + [lrange $arguments 1 end]] if {[$m function.defined $fullname]} { set called [[$m function.get $fullname] ref] - set argvals [lmap arg $arguments {my LoadOrLiteral $arg}] + set argvals [lmap arg [lrange $arguments 1 end] { + my LoadOrLiteral $arg + }] my IssueInvokeFunction $tgt $called $argvals $vname return {} } if {[dict exist $vtypes $tgt]} { set type [nameOfType [dict get $vtypes $tgt]] if {"FAIL" ni $type || "STRING" ni $type} { my Warn "didn't find implementation of '$fullname'" } } - # Don't need to pre-resolve command names if there's a callframe - if {!callframe($thecallframe)} { - set resolved [my LoadOrLiteral [list literal $name]] - } } - set arguments [list $origname {*}$arguments] set argvals [lmap s $arguments {my LoadOrLiteral $s}] # Dynamic dispatch via direct call is OK, *provided* someone has # fetched the function reference for us. @@ -1292,11 +1293,11 @@ # Must dispatch via the Tcl command API. This is the slowest option # with the least type inference possible (everything goes as a # STRING) but it is a reasonable fallback if nothing else works. - my IssueInvokeCommand $tgt $resolved $arguments $argvals $vname + my IssueInvokeCommand $tgt $arguments $argvals $thens $vname return $arguments } method IssueInvokeFunction {tgt func arguments vname} { upvar 1 callframe callframe thecallframe thecallframe @@ -1323,22 +1324,18 @@ set result [$b frame.pack $callframe $result] } my StoreResult $tgt $result } - method IssueInvokeCommand {tgt resolved arguments argvals vname} { + method IssueInvokeCommand {tgt arguments argvals thens vname} { upvar 1 callframe callframe thecallframe thecallframe set types [lmap s $arguments {my ValueTypes $s}] - if {$resolved ne ""} { - # FIXME: this causes wrong "wrong # args" messages - set argvals [lreplace $argvals 0 0 $resolved] - } set vector [$b buildVector $types $argvals] set result [$b invoke $vector \ [expr {callframe($thecallframe)}] $callframe \ - $errorCode $vname] + $thens $errorCode $vname] $b clearVector $arguments $vector $types # Result type is now FAIL STRING, always. my SetErrorLine $errorCode [$b maybe $result] if {callframe($thecallframe)} { set result [$b frame.pack $callframe $result] @@ -1354,26 +1351,27 @@ # Parameters: # callframe - # The callframe. # operation - # The quadcode descriptor for the instruction. + # thens - The current namespace, used for command resolution. # # Results: # The set of arguments that might have been consumed in the operation # (for cleanup by the caller of this method). - method IssueInvokeExpanded {callframe operation} { + method IssueInvokeExpanded {callframe operation thens} { set arguments [lassign $operation opcode tgt thecallframe] set vname [my LocalVarName $tgt] set expandPositions [lmap s $arguments { expr {"EXPANDED" in [my OperandType $s]} }] set argvals [lmap s $arguments {my LoadOrLiteral $s}] set types [lmap s $arguments {my ValueTypes $s}] set vector [$b buildVector $types $argvals] set flags [$b buildBitArray $expandPositions] - set result [$b invokeExpanded $vector $flags $errorCode $vname] + set result [$b invokeExpanded $vector $flags $thens $errorCode $vname] my SetErrorLine $errorCode [$b maybe $result] my StoreResult $tgt [$b frame.pack $callframe $result] $b clearVector $arguments $vector $types return $arguments } Index: codegen/stdlib.tcl ================================================================== --- codegen/stdlib.tcl +++ codegen/stdlib.tcl @@ -4200,22 +4200,24 @@ my ret [my eq $code $0] } ##### Function tcl.invoke.command ##### # - # Type signature: objc:int * objv:STRING* * ecvar:int* -> STRING? + # Type signature: objc:int * objv:STRING* * nsptr:Namespace* + # * ecvar:int* -> STRING? # # Calls the Tcl interpreter to invoke a Tcl command, and packs the # result into a STRING FAIL. set f [$module local "tcl.invoke.command" \ - STRING?<-int,STRING*,CALLFRAME,int*] - params objc objv frame ecvar + STRING?<-int,STRING*,CALLFRAME,Namespace*,int*] + params objc objv frame nsptr ecvar build { - noalias $objv $frame $ecvar - nonnull $objv $ecvar + noalias $objv $frame $nsptr $ecvar + nonnull $objv $nsptr $ecvar set interp [$api tclInterp] + my storeInStruct $interp Interp.lookupNsPtr $nsptr my condBr [my nonnull $frame] $frameInvoke $stdInvoke label stdInvoke "invoke.standard" set code1 [$api Tcl_EvalObjv $interp $objc $objv $0] my condBr [my eq $code1 $0] $ok $fail label frameInvoke "invoke.with.callframe" @@ -4235,23 +4237,23 @@ my ret [my fail STRING $code] } ##### Function tcl.invoke.expanded ##### # - # Type signature: objc:int * objv:STRING* * flags:bool* * ecvar:int* - # -> STRING? + # Type signature: objc:int * objv:STRING* * flags:bool* + # * nsptr:Namespace* * ecvar:int* -> STRING? # # Calls the Tcl interpreter to invoke a Tcl command, first expanding # the arguments indicate by the flags array (which will have objc # elements in it), and packs the result into a STRING FAIL. set f [$module local "tcl.invoke.expanded" \ - STRING?<-int,STRING*,bool*,int*] - params objc objv flags ecvar + STRING?<-int,STRING*,bool*,Namespace*,int*] + params objc objv flags nsptr ecvar build { - noalias $objv $flags $ecvar - nonnull $objv $flags $ecvar + noalias $objv $flags $nsptr $ecvar + nonnull $objv $flags $nsptr $ecvar set tclobjSize [my cast(int) [my sizeof STRING]] set interp [$api tclInterp] set iPtr [my alloc int "i"] set jPtr [my alloc int "j"] set lenPtr [my alloc int "len"] @@ -4310,10 +4312,11 @@ my br $expansionNext label expansionNext "next.expansion" my store [my add $i $1] $iPtr my br $expansionTest label invoke: + my storeInStruct $interp Interp.lookupNsPtr $nsptr set code [$api Tcl_EvalObjv $interp $len $ary $0] $api ckfree $ary my condBr [my eq $code $0] $ok $fail label ok: set result [$api Tcl_GetObjResult $interp] Index: quadcode/builtin_specials.tcl ================================================================== --- quadcode/builtin_specials.tcl +++ quadcode/builtin_specials.tcl @@ -22,15 +22,17 @@ oo::define quadcode::specializer method frameEffect___lsort {q} { if {[lindex $q 0] eq "invokeExpanded"} { # lsort with {*} for the args - punt - my diagnostic error "lsort with argument expansion is not supported yet" - return {reads 0 writes 0 readsNonLocal {} writesNonLocal {}} + return { + reads 0 writes 0 readsNonLocal {} writesNonLocal {} + error "lsort with argument expansion is not supported yet" + } } - # Only [lsort - command] has an interesting frame effect + # Only [lsort -command] has an interesting frame effect # Only [lsort -command] might use callframe data lassign [my parse___lsort $q] usesCommand command if {!$usesCommand} { @@ -40,12 +42,14 @@ # TODO: We can't analyze [lsort -command] yet, but we could. # What it would take is to generate bytecode for the # command prefix with two dummy arguments, and then # determine the effect of the bytecode on the callframe. - my diagnostic error "lsort -command is not supported yet" - return {reads 0 writes 0 readsNonLocal {} writesNonLocal {}} + return { + reads 0 writes 0 readsNonLocal {} writesNonLocal {} + error "lsort -command is not supported yet" + } } # quadcode::specializer method frameEffect___regexp -- # @@ -60,11 +64,12 @@ oo::define quadcode::specializer method frameEffect___regexp {q} { # 0 - 'invoke' # 1 - result callframe # 2 - input callframe # 3 - ::regexp - # 4+ - remaining args + # 4 - regexp + # 5+ - remaining args if {[lindex $q 0] eq "invokeExpanded"} { # can't figure out what vars are written, but we know there are # no other untoward side effects @@ -71,11 +76,11 @@ return {reads 0 writes 0} } # Skip over the command line switches - set ind 4 + set ind 5 while {$ind < [llength $q] - 2} { if {[lindex $q $ind 0] ne "literal"} { return {writes 0} } switch -exact -- [lindex $q $ind 1] { @@ -131,21 +136,22 @@ # 0 - 'invoke' # 1 - result callframe # 2 - input callframe # 3 - ::regsub - # 4+ - remaining args + # 4 - regsub + # 5+ - remaining args if {[lindex $q 0] eq "invokeExpanded"} { # can't figure out variable effects but otherwise the command is benign return {reads 0 writes 0} } # Skip over the command line switches - set ind 4 + set ind 5 while {$ind < [llength $q]} { if {[lindex $q $ind 0] ne "literal"} { if {$ind + 3 == [llength $q]} { return {killable Inf noCallFrame {} pure {}} } else { @@ -194,11 +200,11 @@ # Returns a two-element list. The first element is a flag for # whether -command is present; the second is the command provided. oo::define quadcode::specializer method parse___lsort {q} { - set ind 4 + set ind 5 while {$ind + 1 < [llength $q]} { if {[lindex $q $ind 0] eq "literal"} { set opt [lindex $q $ind 1] } else { error "substitution in lsort flags is not supported." Index: quadcode/callframe.tcl ================================================================== --- quadcode/callframe.tcl +++ quadcode/callframe.tcl @@ -503,11 +503,11 @@ # The variables altered by the 'invoke', plus # all aliases, are potentially changed. set aliases {} - set atypes [lmap x [lrange $producer 4 end] { + set atypes [lmap x [my invoke-args $producer] { typeOfOperand $types $x }] lassign [my variablesProducedBy $producer $atypes] \ known wlist if {$known} { @@ -656,11 +656,11 @@ continue } # Determine argument types of the consuming call, which always # begins with some output and a callframe input - set atypes [lmap x [lrange $consumer 4 end] { + set atypes [lmap x [my invoke-args $consumer] { typeOfOperand $types $x }] # Find out what variables that the consumer potentially reads. # Because potentially changed variables may also be unchanged, @@ -938,11 +938,11 @@ if {[lindex $toCF 0] ne "temp"} continue # Is the result a callframe, and can we eliminate it? set toCFType [typeOfOperand $types $toCF] if {$opcode in {"invoke" "invokeExpanded"}} { - set atypes [lmap x [lrange $q 4 end] { + set atypes [lmap x [my invoke-args $q] { typeOfOperand $types $x }] } else { set atypes {} } @@ -1363,5 +1363,36 @@ # If none of the above conditions hold, the callframe reference and # definition can be removed safely from the quad. return 1 } + +# quadcode::transformer method invoke-args -- +# +# Get the real arguments (other than the command name) that will +# be used with the invocation. +# +# Parameters: +# q - Quadcode instruction that produces a callframe. +# +# Results: +# Returns the list of arguments (quadcode values) if they are +# meaningful, otherwise produces an error.. + +oo::define quadcode::transformer method invoke-args {q} { + switch [lindex $q 0 0] { + "invoke" { + return [lrange $q 5 end] + } + "invokeExpanded" { + return [lrange $q 4 end] + } + "" { + # What is going on here? + return + } + default { + return -code error "cannot get invoke arguments from non-invoke\ + opcode '$q'" + } + } +} Index: quadcode/fqcmd.tcl ================================================================== --- quadcode/fqcmd.tcl +++ quadcode/fqcmd.tcl @@ -28,24 +28,24 @@ set b 0 foreach content $bbcontent { set i 0 foreach q $content { if {[lindex $q 0 0] in {"invoke" "invokeExpanded"} - && [lindex $q 3 0] eq "literal"} { + && [lindex $q 3 0] eq "literal"} { set cmdname [lindex $q 3 1] set resolved \ [namespace eval $ns [list namespace which $cmdname]] if {$resolved ne {}} { set cmdname $resolved } if {![catch { namespace eval $ns [list namespace origin $cmdname] } resolved]} { - set cmdname $resolved + set cmdname $resolved } lset bbcontent $b $i 3 1 $cmdname } incr i } incr b } } Index: quadcode/translate.tcl ================================================================== --- quadcode/translate.tcl +++ quadcode/translate.tcl @@ -892,11 +892,12 @@ for {set i $rcount} {$i < $acount} {incr i} { lappend qd [list temp [expr {$depth + $i}]] } my generate-function-param-check $pc $qd # generate the call itself - my quads invoke {temp @callframe} {temp @callframe} {*}$qd + my quads invoke {temp @callframe} {temp @callframe} \ + [lindex $qd 0] {*}$qd my quads retrieveResult $result {temp @callframe} my quads extractCallFrame {temp @callframe} {temp @callframe} my generate-jump [my exception-target $pc catch] maybe $result my quads extractMaybe $result $result } @@ -908,11 +909,12 @@ for {set i 0} {$i < $acount} {incr i} { lappend qd [list temp [expr {$depth + $i}]] } my generate-function-param-check $pc $qd # generate the call itself - my quads invoke {temp @callframe} {temp @callframe} {*}$qd + my quads invoke {temp @callframe} {temp @callframe} \ + [lindex $qd 0] {*}$qd my quads retrieveResult $result {temp @callframe} my quads extractCallFrame {temp @callframe} {temp @callframe} my generate-jump [my exception-target $pc catch] maybe $result my quads extractMaybe $result $result } Index: quadcode/upvar.tcl ================================================================== --- quadcode/upvar.tcl +++ quadcode/upvar.tcl @@ -536,18 +536,24 @@ } } "invoke" { set did 1 - set argList [lassign $q opcode cfout cfin cmdName] + set argList [lassign $q opcode cfout cfin resolvedcCmdName orignalCmdName] set typeList [lmap arg $argList {typeOfOperand $types $arg}] if {[catch { $specializer frameEffect $q $typeList } attrs]} { my diagnostic error $b $pc $attrs set attrs {readsAny 1 readsNonLocal 1 \ writesAny 1 writesNonLocal 1} + } + foreach dgtype {error warning} { + if {[dict exists $attrs $dgtype]} { + my diagnostic $dgtype $b $pc [dict get $attrs $dgtype] + dict unset attrs $dgtype + } } my upvarInvoke result $aliasInfo $attrs $q $typeList } } @@ -639,14 +645,11 @@ # Side effects: # Records the effect of the 'invoke' on the current callframe. oo::define quadcode::transformer method upvarInvoke {resultV aliasInfo effect q typeList} { - - upvar 1 $resultV result - set callframe [lindex $q 1] # Record purity if {![dict exists $effect pure]} { Index: quadcode/varargs.tcl ================================================================== --- quadcode/varargs.tcl +++ quadcode/varargs.tcl @@ -112,11 +112,11 @@ oo::define quadcode::transformer method varargsRewriteInvoke {b pc q} { set newqds {} # Take apart the quad - set argv [lassign $q opcode cfout cfin calleeLit] + set argv [lassign $q opcode cfout cfin calleeLit origCallee] # We care only about 'invoke' instructions where the procedure name # is known a priori, the expected args are known, and the # target procedure is compiled. if {[lindex $calleeLit 0] ne "literal" @@ -151,11 +151,11 @@ } # Make a new quad that passes all supplied params that # don't go in 'args'. Set 'paramsLeft' to the list of # parameters that weren't filled. - set newq [list invoke $cfout $cfin $calleeLit \ + set newq [list invoke $cfout $cfin $calleeLit $origCallee \ {*}[lrange $argv 0 [expr {$nonargs-1}]]] set paramsleft [lrange $arginfo [llength $argv] [expr {$nonargs-1}]] # If we need to fill in optional parameters, do that now foreach param $paramsleft { @@ -230,11 +230,11 @@ } # Make the first part of the 'invoke' instruction that will # replace the 'invokeExpanded' - set newq [list invoke $cfout $cfin $calleeLit] + set newq [list invoke $cfout $cfin $calleeLit $calleeLit] # We are going to be doing major surgery on the basic block. # Remove the 'invokeExpanded' and all following instructions # from the block. Unlink the block from its successors, and # remove ud- and du-chaining for the removed instructions.