2 % (c) The GRASP/AQUA Project, Glasgow University, 1992-1998
4 % $Id: CgClosure.lhs,v 1.37 1999/11/02 15:05:43 simonmar Exp $
6 \section[CgClosure]{Code generation for closures}
8 This module provides the support code for @StgToAbstractC@ to deal
9 with {\em closures} on the RHSs of let(rec)s. See also
10 @CgCon@, which deals with constructors.
13 module CgClosure ( cgTopRhsClosure,
16 closureCodeBody ) where
18 #include "HsVersions.h"
20 import {-# SOURCE #-} CgExpr ( cgExpr )
26 import AbsCUtils ( mkAbstractCs, getAmodeRep )
27 import CgBindery ( getCAddrMode, getArgAmodes,
28 getCAddrModeAndInfo, bindNewToNode,
30 bindNewToReg, bindArgsToRegs,
31 stableAmodeIdInfo, heapIdInfo, CgIdInfo
33 import CgUpdate ( pushUpdateFrame )
34 import CgHeapery ( allocDynClosure,
35 fetchAndReschedule, yield, -- HWL
36 fastEntryChecks, thunkChecks
38 import CgStackery ( mkTaggedVirtStkOffsets, freeStackSlots )
39 import CgUsages ( adjustSpAndHp, setRealAndVirtualSp, getVirtSp,
40 getSpRelOffset, getHpRelOffset
42 import CLabel ( CLabel, mkClosureLabel, mkFastEntryLabel,
43 mkRednCountsLabel, mkInfoTableLabel
45 import ClosureInfo -- lots and lots of stuff
46 import CmdLineOpts ( opt_GranMacros, opt_SccProfilingOn, opt_DoTickyProfiling )
48 import Id ( Id, idName, idType, idPrimRep )
49 import Name ( Name, isLocalName )
50 import Module ( Module, pprModule )
51 import ListSetOps ( minusList )
52 import PrimRep ( PrimRep(..) )
53 import PprType ( showTypeCategory )
55 import CmdLineOpts ( opt_SccProfilingOn )
58 import Name ( nameOccName )
59 import OccName ( occNameFS )
61 getWrapperArgTypeCategories = panic "CgClosure.getWrapperArgTypeCategories (ToDo)"
64 %********************************************************
66 \subsection[closures-no-free-vars]{Top-level closures}
68 %********************************************************
70 For closures bound at top level, allocate in static space.
71 They should have no free variables.
75 -> CostCentreStack -- Optional cost centre annotation
80 -> FCode (Id, CgIdInfo)
82 cgTopRhsClosure id ccs binder_info args body lf_info
83 = -- LAY OUT THE OBJECT
85 closure_info = layOutStaticNoFVClosure name lf_info
88 -- BUILD THE OBJECT (IF NECESSARY)
89 ({- if staticClosureRequired name binder_info lf_info
91 (if opt_SccProfilingOn
94 closure_label -- Labelled with the name on lhs of defn
96 (mkCCostCentreStack ccs)
100 closure_label -- Labelled with the name on lhs of defn
110 -- GENERATE THE INFO TABLE (IF NECESSARY)
111 forkClosureBody (closureCodeBody binder_info closure_info
116 returnFC (id, cg_id_info)
119 closure_label = mkClosureLabel name
120 cg_id_info = stableAmodeIdInfo id (CLbl closure_label PtrRep) lf_info
123 %********************************************************
125 \subsection[non-top-level-closures]{Non top-level closures}
127 %********************************************************
129 For closures with free vars, allocate in heap.
134 -> CostCentreStack -- Optional cost centre annotation
140 -> [StgArg] -- payload
141 -> FCode (Id, CgIdInfo)
143 cgStdRhsClosure binder cc binder_info fvs args body lf_info payload
144 -- AHA! A STANDARD-FORM THUNK
146 -- LAY OUT THE OBJECT
147 getArgAmodes payload `thenFC` \ amodes ->
149 (closure_info, amodes_w_offsets)
150 = layOutDynClosure (idName binder) getAmodeRep amodes lf_info
152 (use_cc, blame_cc) = chooseDynCostCentres cc args fvs body
155 allocDynClosure closure_info use_cc blame_cc amodes_w_offsets
157 `thenFC` \ heap_offset ->
160 returnFC (binder, heapIdInfo binder heap_offset lf_info)
163 is_std_thunk = isStandardFormThunk lf_info
166 Here's the general case.
170 -> CostCentreStack -- Optional cost centre annotation
176 -> FCode (Id, CgIdInfo)
178 cgRhsClosure binder cc binder_info fvs args body lf_info
180 -- LAY OUT THE OBJECT
182 -- If the binder is itself a free variable, then don't store
183 -- it in the closure. Instead, just bind it to Node on entry.
184 -- NB we can be sure that Node will point to it, because we
185 -- havn't told mkClosureLFInfo about this; so if the binder
186 -- *was* a free var of its RHS, mkClosureLFInfo thinks it *is*
187 -- stored in the closure itself, so it will make sure that
188 -- Node points to it...
190 is_elem = isIn "cgRhsClosure"
192 binder_is_a_fv = binder `is_elem` fvs
193 reduced_fvs = if binder_is_a_fv
194 then fvs `minusList` [binder]
197 mapFCs getCAddrModeAndInfo reduced_fvs `thenFC` \ amodes_and_info ->
199 fvs_w_amodes_and_info = reduced_fvs `zip` amodes_and_info
201 closure_info :: ClosureInfo
202 bind_details :: [((Id, (CAddrMode, LambdaFormInfo)), VirtualHeapOffset)]
204 (closure_info, bind_details)
205 = layOutDynClosure (idName binder) get_kind fvs_w_amodes_and_info lf_info
207 bind_fv ((id, (_, lf_info)), offset) = bindNewToNode id offset lf_info
209 amodes_w_offsets = [(amode,offset) | ((_, (amode,_)), offset) <- bind_details]
211 get_kind (id, amode_and_info) = idPrimRep id
213 -- BUILD ITS INFO TABLE AND CODE
216 mapCs bind_fv bind_details `thenC`
218 -- Bind the binder itself, if it is a free var
219 (if binder_is_a_fv then
220 bindNewToReg binder node lf_info
225 closureCodeBody binder_info closure_info cc args body
230 (use_cc, blame_cc) = chooseDynCostCentres cc args fvs body
232 allocDynClosure closure_info use_cc blame_cc amodes_w_offsets
233 ) `thenFC` \ heap_offset ->
236 returnFC (binder, heapIdInfo binder heap_offset lf_info)
239 %************************************************************************
241 \subsection[code-for-closures]{The code for closures}
243 %************************************************************************
246 closureCodeBody :: StgBinderInfo
247 -> ClosureInfo -- Lots of information about this closure
248 -> CostCentreStack -- Optional cost centre attached to closure
254 There are two main cases for the code for closures. If there are {\em
255 no arguments}, then the closure is a thunk, and not in normal form.
256 So it should set up an update frame (if it is shared). Also, it has
257 no argument satisfaction check, so fast and slow entry-point labels
261 closureCodeBody binder_info closure_info cc [] body
262 = -- thunks cannot have a primitive type!
263 getAbsC body_code `thenFC` \ body_absC ->
264 moduleName `thenFC` \ mod_name ->
266 absC (CClosureInfoAndCode closure_info body_absC Nothing
269 cl_descr mod_name = closureDescription mod_name (closureName closure_info)
271 body_label = entryLabelFromCI closure_info
273 is_box = case body of { StgApp fun [] -> True; _ -> False }
275 body_code = profCtrC SLIT("TICK_ENT_THK") [] `thenC`
276 thunkWrapper closure_info body_label (
277 -- We only enter cc after setting up update so that cc
278 -- of enclosing scope will be recorded in update frame
279 -- CAF/DICT functions will be subsumed by this enclosing cc
280 enterCostCentreCode closure_info cc IsThunk is_box `thenC`
284 If there is {\em at least one argument}, then this closure is in
285 normal form, so there is no need to set up an update frame. On the
286 other hand, we do have to check that there are enough args, and
287 perform an update if not!
289 The Macros for GrAnSim are produced at the beginning of the
290 argSatisfactionCheck (by calling fetchAndReschedule). There info if
291 Node points to closure is available. -- HWL
294 closureCodeBody binder_info closure_info cc all_args body
295 = getEntryConvention name lf_info
296 (map idPrimRep all_args) `thenFC` \ entry_conv ->
298 -- get the current virtual Sp (it might not be zero, eg. if we're
299 -- compiling a let-no-escape).
300 getVirtSp `thenFC` \vSp ->
303 -- Figure out what is needed and what isn't
305 -- SDM: need everything for now in case the heap/stack check refers
307 slow_code_needed = True
308 --slowFunEntryCodeRequired name binder_info entry_conv
309 info_table_needed = True
310 --funInfoTableRequired name binder_info lf_info
312 -- Arg mapping for standard (slow) entry point; all args on stack,
314 (sp_all_args, arg_offsets, arg_tags)
315 = mkTaggedVirtStkOffsets vSp idPrimRep all_args
317 -- Arg mapping for the fast entry point; as many args as poss in
318 -- registers; the rest on the stack
319 -- arg_regs are the registers used for arg passing
320 -- stk_args are the args which are passed on the stack
322 -- Args passed on the stack are tagged, but the tags may not
323 -- actually be present (just gaps) if the function is called
324 -- by jumping directly to the fast entry point.
326 arg_regs = case entry_conv of
327 DirectEntry lbl arity regs -> regs
328 other -> panic "closureCodeBody:arg_regs"
330 num_arg_regs = length arg_regs
332 (reg_args, stk_args) = splitAt num_arg_regs all_args
334 (sp_stk_args, stk_offsets, stk_tags)
335 = mkTaggedVirtStkOffsets vSp idPrimRep stk_args
337 -- HWL; Note: empty list of live regs in slow entry code
338 -- Old version (reschedule combined with heap check);
339 -- see argSatisfactionCheck for new version
340 --slow_entry_code = forceHeapCheck [node] True slow_entry_code'
341 -- where node = UnusedReg PtrRep 1
342 --slow_entry_code = forceHeapCheck [] True slow_entry_code'
345 = profCtrC SLIT("TICK_ENT_FUN_STD") [] `thenC`
347 -- Bind args, and record expected position of stk ptrs
348 mapCs bindNewToStack arg_offsets `thenC`
349 setRealAndVirtualSp sp_all_args `thenC`
351 argSatisfactionCheck closure_info `thenC`
353 -- OK, so there are enough args. Now we need to stuff as
354 -- many of them in registers as the fast-entry code
355 -- expects. Note that the zipWith will give up when it hits
356 -- the end of arg_regs.
358 mapFCs getCAddrMode all_args `thenFC` \ stk_amodes ->
359 absC (mkAbstractCs (zipWith assign_to_reg arg_regs stk_amodes))
362 -- Now adjust real stack pointers (no need to adjust Hp,
363 -- but call this function for convenience).
364 adjustSpAndHp sp_stk_args `thenC`
366 absC (CFallThrough (CLbl fast_label CodePtrRep))
368 assign_to_reg reg_id amode = CAssign (CReg reg_id) amode
371 -- Old version (reschedule combined with heap check);
372 -- see argSatisfactionCheck for new version
373 -- fast_entry_code = forceHeapCheck [] True fast_entry_code'
376 = moduleName `thenFC` \ mod_name ->
377 profCtrC SLIT("TICK_CTR") [
378 CLbl ticky_ctr_label DataPtrRep,
379 mkCString (_PK_ (ppr_for_ticky_name mod_name name)),
380 mkIntCLit stg_arity, -- total # of args
381 mkIntCLit sp_stk_args, -- # passed on stk
382 mkCString (_PK_ (map (showTypeCategory . idType) all_args))
385 profCtrC SLIT("TICK_ENT_FUN_DIRECT") [
386 CLbl ticky_ctr_label DataPtrRep
389 -- Nuked for now; see comment at end of file
390 -- CString (_PK_ (show_wrapper_name wrapper_maybe)),
391 -- CString (_PK_ (show_wrapper_arg_kinds wrapper_maybe))
394 -- Bind args to regs/stack as appropriate, and
395 -- record expected position of sps.
396 bindArgsToRegs reg_args arg_regs `thenC`
397 mapCs bindNewToStack stk_offsets `thenC`
398 setRealAndVirtualSp sp_stk_args `thenC`
400 -- free up the stack slots containing tags
401 freeStackSlots (map fst stk_tags) `thenC`
403 -- Enter the closures cc, if required
404 enterCostCentreCode closure_info cc IsFunction False `thenC`
407 funWrapper closure_info arg_regs stk_tags info_label (cgExpr body)
410 setTickyCtrLabel ticky_ctr_label (
412 -- Make a labelled code-block for the slow and fast entry code
413 forkAbsC (if slow_code_needed then slow_entry_code else absC AbsCNop)
414 `thenFC` \ slow_abs_c ->
415 forkAbsC fast_entry_code `thenFC` \ fast_abs_c ->
416 moduleName `thenFC` \ mod_name ->
418 -- Now either construct the info table, or put the fast code in alone
419 -- (We never have slow code without an info table)
420 -- XXX probably need the info table and slow entry code in case of
421 -- a heap check failure.
423 if info_table_needed then
424 CClosureInfoAndCode closure_info slow_abs_c (Just fast_abs_c)
427 CCodeBlock fast_label fast_abs_c
431 ticky_ctr_label = mkRednCountsLabel name
433 stg_arity = length all_args
434 lf_info = closureLFInfo closure_info
436 cl_descr mod_name = closureDescription mod_name name
438 -- Manufacture labels
439 name = closureName closure_info
440 fast_label = mkFastEntryLabel name stg_arity
441 info_label = mkInfoTableLabel name
444 -- When printing the name of a thing in a ticky file, we want to
445 -- give the module name even for *local* things. We print
446 -- just "x (M)" rather that "M.x" to distinguish them from the global kind.
447 ppr_for_ticky_name mod_name name
448 | isLocalName name = showSDocDebug (ppr name <+> (parens (ppr mod_name)))
449 | otherwise = showSDocDebug (ppr name)
452 For lexically scoped profiling we have to load the cost centre from
453 the closure entered, if the costs are not supposed to be inherited.
454 This is done immediately on entering the fast entry point.
456 Load current cost centre from closure, if not inherited.
457 Node is guaranteed to point to it, if profiling and not inherited.
460 data IsThunk = IsThunk | IsFunction -- Bool-like, local
466 :: ClosureInfo -> CostCentreStack
468 -> Bool -- is_box: this closure is a special box introduced by SCCfinal
471 enterCostCentreCode closure_info ccs is_thunk is_box
472 = if not opt_SccProfilingOn then
475 ASSERT(not (noCCSAttached ccs))
477 if isSubsumedCCS ccs then
478 ASSERT(isToplevClosure closure_info)
479 ASSERT(is_thunk == IsFunction)
480 costCentresC SLIT("ENTER_CCS_FSUB") []
482 else if isCurrentCCS ccs then
483 if re_entrant && not is_box
484 then costCentresC SLIT("ENTER_CCS_FCL") [CReg node]
485 else costCentresC SLIT("ENTER_CCS_TCL") [CReg node]
487 else if isCafCCS ccs then
488 ASSERT(isToplevClosure closure_info)
489 ASSERT(is_thunk == IsThunk)
490 -- might be a PAP, in which case we want to subsume costs
492 then costCentresC SLIT("ENTER_CCS_FSUB") []
493 else costCentresC SLIT("ENTER_CCS_CAF") c_ccs
495 else panic "enterCostCentreCode"
498 c_ccs = [mkCCostCentreStack ccs]
499 re_entrant = closureReEntrant closure_info
502 %************************************************************************
504 \subsubsection[pre-closure-code-stuff]{Pre-closure-code code}
506 %************************************************************************
508 The argument-satisfaction check code is placed after binding
509 the arguments to their stack locations. Hence, the virtual stack
510 pointer is pointing after all the args, and virtual offset 1 means
511 the base of frame and hence most distant arg. Hence
512 virtual offset 0 is just beyond the most distant argument; the
513 relative offset of this word tells how many words of arguments
517 argSatisfactionCheck :: ClosureInfo -> Code
519 argSatisfactionCheck closure_info
521 = nodeMustPointToIt (closureLFInfo closure_info) `thenFC` \ node_points ->
524 emit_gran_macros = opt_GranMacros
528 -- absC (CMacroStmt GRAN_FETCH []) `thenC`
529 -- forceHeapCheck [] node_points (absC AbsCNop) `thenC`
532 then fetchAndReschedule [] node_points
533 else yield [] node_points
534 else absC AbsCNop) `thenC`
536 getSpRelOffset 0 `thenFC` \ (SpRel sp) ->
539 rel_arg = mkIntCLit off
543 absC (CMacroStmt ARGS_CHK [rel_arg]) -- node already points
545 absC (CMacroStmt ARGS_CHK_LOAD_NODE [rel_arg, set_Node_to_this])
547 -- We must tell the arg-satis macro whether Node is pointing to
548 -- the closure or not. If it isn't so pointing, then we give to
549 -- the macro the (static) address of the closure.
551 set_Node_to_this = CLbl (closureLabelFromCI closure_info) PtrRep
554 %************************************************************************
556 \subsubsection[closure-code-wrappers]{Wrappers around closure code}
558 %************************************************************************
561 thunkWrapper:: ClosureInfo -> CLabel -> Code -> Code
562 thunkWrapper closure_info lbl thunk_code
563 = -- Stack and heap overflow checks
564 nodeMustPointToIt (closureLFInfo closure_info) `thenFC` \ node_points ->
567 emit_gran_macros = opt_GranMacros
569 -- HWL: insert macros for GrAnSim; 2 versions depending on liveness of node
570 -- (we prefer fetchAndReschedule-style context switches to yield ones)
573 then fetchAndReschedule [] node_points
574 else yield [] node_points
575 else absC AbsCNop) `thenC`
577 -- stack and/or heap checks
578 thunkChecks lbl node_points (
580 -- Overwrite with black hole if necessary
581 blackHoleIt closure_info node_points `thenC`
583 setupUpdate closure_info ( -- setupUpdate *encloses* the rest
585 -- Finally, do the business
589 funWrapper :: ClosureInfo -- Closure whose code body this is
590 -> [MagicId] -- List of argument registers (if any)
591 -> [(VirtualSpOffset,Int)] -- tagged stack slots
592 -> CLabel -- info table for heap check ret.
593 -> Code -- Body of function being compiled
595 funWrapper closure_info arg_regs stk_tags info_label fun_body
596 = -- Stack overflow check
597 nodeMustPointToIt (closureLFInfo closure_info) `thenFC` \ node_points ->
599 emit_gran_macros = opt_GranMacros
603 then yield arg_regs node_points
604 else absC AbsCNop) `thenC`
606 -- heap and/or stack checks
607 fastEntryChecks arg_regs stk_tags info_label node_points (
609 -- Finally, do the business
615 %************************************************************************
617 \subsubsubsection[update-and-BHs]{Update and black-hole wrappers}
619 %************************************************************************
623 blackHoleIt :: ClosureInfo -> Bool -> Code -- Only called for closures with no args
625 blackHoleIt closure_info node_points
626 = if blackHoleOnEntry closure_info && node_points
629 info_label = infoTableLabelFromCI closure_info
630 args = [ CLbl info_label DataPtrRep ]
632 absC (if closureSingleEntry(closure_info) then
633 CMacroStmt UPD_BH_SINGLE_ENTRY args
635 CMacroStmt UPD_BH_UPDATABLE args)
641 setupUpdate :: ClosureInfo -> Code -> Code -- Only called for closures with no args
642 -- Nota Bene: this function does not change Node (even if it's a CAF),
643 -- so that the cost centre in the original closure can still be
644 -- extracted by a subsequent ENTER_CC_TCL
646 -- I've tidied up the code for this function, but it should still do the same as
647 -- it did before (modulo ticky stuff). KSW 1999-04.
648 setupUpdate closure_info code
649 = if closureReEntrant closure_info
653 case (closureUpdReqd closure_info, isStaticClosure closure_info) of
654 (False,False) -> profCtrC SLIT("TICK_UPDF_OMITTED") [] `thenC`
656 (False,True ) -> (if opt_DoTickyProfiling
658 -- blackhole the SE CAF
659 link_caf seCafBlackHoleClosureInfo `thenFC` \ _ -> nopC
662 profCtrC SLIT("TICK_UPD_CAF_BH_SINGLE_ENTRY") [mkCString cl_name] `thenC`
663 profCtrC SLIT("TICK_UPDF_OMITTED") [] `thenC`
665 (True ,False) -> pushUpdateFrame (CReg node) code
666 (True ,True ) -> -- blackhole the (updatable) CAF:
667 link_caf cafBlackHoleClosureInfo `thenFC` \ update_closure ->
668 profCtrC SLIT("TICK_UPD_CAF_BH_UPDATABLE") [mkCString cl_name] `thenC`
669 pushUpdateFrame update_closure code
671 cl_name :: FAST_STRING
672 cl_name = (occNameFS . nameOccName . closureName) closure_info
674 link_caf :: (ClosureInfo -> ClosureInfo) -- function yielding BH closure_info
675 -> FCode CAddrMode -- Returns amode for closure to be updated
677 = -- To update a CAF we must allocate a black hole, link the CAF onto the
678 -- CAF list, then update the CAF to point to the fresh black hole.
679 -- This function returns the address of the black hole, so it can be
680 -- updated with the new value when available.
682 -- Alloc black hole specifying CC_HDR(Node) as the cost centre
683 -- Hack Warning: Using a CLitLit to get CAddrMode !
685 use_cc = CLitLit SLIT("CCS_HDR(R1.p)") PtrRep
688 allocDynClosure (bhCI closure_info) use_cc blame_cc [] `thenFC` \ heap_offset ->
689 getHpRelOffset heap_offset `thenFC` \ hp_rel ->
690 let amode = CAddr hp_rel
692 absC (CMacroStmt UPD_CAF [CReg node, amode]) `thenC`
696 %************************************************************************
698 \subsection[CgClosure-Description]{Profiling Closure Description.}
700 %************************************************************************
702 For "global" data constructors the description is simply occurrence
703 name of the data constructor itself (see \ref{CgConTbls-info-tables}).
705 Otherwise it is determind by @closureDescription@ from the let
709 closureDescription :: Module -- Module
710 -> Name -- Id of closure binding
713 -- Not called for StgRhsCon which have global info tables built in
714 -- CgConTbls.lhs with a description generated from the data constructor
716 closureDescription mod_name name
726 chooseDynCostCentres ccs args fvs body
728 use_cc -- cost-centre we record in the object
729 = if currentOrSubsumedCCS ccs
730 then CReg CurCostCentre
731 else mkCCostCentreStack ccs
733 blame_cc -- cost-centre on whom we blame the allocation
734 = case (args, fvs, body) of
735 ([], _, StgApp fun [{-no args-}])
736 -> mkCCostCentreStack overheadCCS
739 -- if it's an utterly trivial RHS, then it must be
740 -- one introduced by boxHigherOrderArgs for profiling,
741 -- so we charge it to "OVERHEAD".
743 -- This looks like a HACK to me --SDM
750 ========================================================================
751 OLD CODE THAT EMITTED INFORMATON FOR QUANTITATIVE ANALYSIS
753 It's pretty wierd, so I've nuked it for now. SLPJ Nov 96
756 getWrapperArgTypeCategories
757 :: Type -- wrapper's type
758 -> StrictnessInfo bdee -- strictness info about its args
761 getWrapperArgTypeCategories _ NoStrictnessInfo = Nothing
762 getWrapperArgTypeCategories _ BottomGuaranteed
763 = trace "getWrapperArgTypeCategories:BottomGuaranteed!" Nothing -- wrong
764 getWrapperArgTypeCategories _ (StrictnessInfo [] _) = Nothing
766 getWrapperArgTypeCategories ty (StrictnessInfo arg_info _)
767 = Just (mkWrapperArgTypeCategories ty arg_info)
769 mkWrapperArgTypeCategories
770 :: Type -- wrapper's type
771 -> [Demand] -- info about its arguments
772 -> String -- a string saying lots about the args
774 mkWrapperArgTypeCategories wrapper_ty wrap_info
775 = case (splitFunTy_maybe wrapper_ty) of { Just (arg_tys,_) ->
776 map do_one (wrap_info `zip` (map showTypeCategory arg_tys)) }
778 -- ToDo: this needs FIXING UP (it was a hack anyway...)
779 do_one (WwPrim, _) = 'P'
780 do_one (WwEnum, _) = 'E'
781 do_one (WwStrict, arg_ty_char) = arg_ty_char
782 do_one (WwUnpack _ _ _, arg_ty_char)
783 = if arg_ty_char `elem` "CIJFDTS"
784 then toLower arg_ty_char
785 else if arg_ty_char == '+' then 't'
786 else trace ("mkWrapp..:funny char:"++[arg_ty_char]) '-'
787 do_one (other_wrap_info, _) = '-'