From patchwork Wed Feb 6 10:11:28 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arnaud Charlet X-Patchwork-Id: 218525 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from sourceware.org (server1.sourceware.org [209.132.180.131]) by ozlabs.org (Postfix) with SMTP id 4948B2C0311 for ; Wed, 6 Feb 2013 21:11:56 +1100 (EST) Comment: DKIM? See http://www.dkim.org DKIM-Signature: v=1; a=rsa-sha1; c=relaxed/relaxed; d=gcc.gnu.org; s=default; x=1360750317; h=Comment: DomainKey-Signature:Received:Received:Received:Received:Received: Received:Received:Date:From:To:Cc:Subject:Message-ID: MIME-Version:Content-Type:Content-Disposition:User-Agent: Mailing-List:Precedence:List-Id:List-Unsubscribe:List-Archive: List-Post:List-Help:Sender:Delivered-To; bh=nNghg7Dmw05pZ5vE/9XQ bkHohwk=; b=YPFnCtHinSyxYxY9bHvia2X18UIPwDaf6e/1RU+OeHR9OgQMwslq X36KsYaqS2EilVkoBk6tLzrnv1XYXgrGaG5u4m7IR6CtF+6Zd8p6DzOg4qPKcRMX NwJAHgwmTr2eml1O5PB71tK0dFfAEOZANb0H7k2FeHmsHjsEMG+JKnk= Comment: DomainKeys? See http://antispam.yahoo.com/domainkeys DomainKey-Signature: a=rsa-sha1; q=dns; c=nofws; s=default; d=gcc.gnu.org; h=Received:Received:X-SWARE-Spam-Status:X-Spam-Check-By:Received:Received:Received:Received:Received:Date:From:To:Cc:Subject:Message-ID:MIME-Version:Content-Type:Content-Disposition:User-Agent:Mailing-List:Precedence:List-Id:List-Unsubscribe:List-Archive:List-Post:List-Help:Sender:Delivered-To; b=Jtar3DGUSmafCcT8dgt5CsqOAiRqBNYeju4XjgauHKIzUVHnuw/fs6MVW4wmZ+ FCDKwYVj545FnKwRmrQ86BQO/OvBf5jKYjN87qYN9skIOw5ZiYBrgSuQBlu9Q2P3 wpyq3ByaP3R3aOatpCfplgMDiVpuhvXT1svhpjEUeAm2c=; Received: (qmail 16761 invoked by alias); 6 Feb 2013 10:11:47 -0000 Received: (qmail 16735 invoked by uid 22791); 6 Feb 2013 10:11:45 -0000 X-SWARE-Spam-Status: No, hits=-1.8 required=5.0 tests=AWL, BAYES_00, RCVD_IN_HOSTKARMA_NO X-Spam-Check-By: sourceware.org Received: from rock.gnat.com (HELO rock.gnat.com) (205.232.38.15) by sourceware.org (qpsmtpd/0.43rc1) with ESMTP; Wed, 06 Feb 2013 10:11:30 +0000 Received: from localhost (localhost.localdomain [127.0.0.1]) by filtered-rock.gnat.com (Postfix) with ESMTP id 25AE92E5BF; Wed, 6 Feb 2013 05:11:29 -0500 (EST) Received: from rock.gnat.com ([127.0.0.1]) by localhost (rock.gnat.com [127.0.0.1]) (amavisd-new, port 10024) with LMTP id T0m7DUtnP8OF; Wed, 6 Feb 2013 05:11:29 -0500 (EST) Received: from kwai.gnat.com (kwai.gnat.com [205.232.38.4]) by rock.gnat.com (Postfix) with ESMTP id F0CAB2E1C3; Wed, 6 Feb 2013 05:11:28 -0500 (EST) Received: by kwai.gnat.com (Postfix, from userid 4192) id F084F3FF09; Wed, 6 Feb 2013 05:11:28 -0500 (EST) Date: Wed, 6 Feb 2013 05:11:28 -0500 From: Arnaud Charlet To: gcc-patches@gcc.gnu.org Cc: Hristian Kirtchev Subject: [Ada] Missing finalization of temporary function results in case and if Message-ID: <20130206101128.GA29602@adacore.com> MIME-Version: 1.0 Content-Disposition: inline User-Agent: Mutt/1.5.20 (2009-06-14) Mailing-List: contact gcc-patches-help@gcc.gnu.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Archive: List-Post: List-Help: Sender: gcc-patches-owner@gcc.gnu.org Delivered-To: mailing list gcc-patches@gcc.gnu.org This patch adds machinery to properly finalize temporary controlled function results that appear in N_Expression_With_Actions nodes as well as case and if expressions. In general, such temporaries must be finalized after the related context is elaborated/evaluated. ------------ -- Source -- ------------ -- types.ads with Ada.Finalization; use Ada.Finalization; package Types is type Ctrl is new Controlled with record Data : Natural := 1234; end record; procedure Finalize (Obj : in out Ctrl); function Make_Ctrl (Val : Natural) return Ctrl; end Types; -- types.adb with Ada.Text_IO; use Ada.Text_IO; package body Types is procedure Finalize (Obj : in out Ctrl) is begin Obj.Data := 0; Put_Line (" fin"); end Finalize; function Make_Ctrl (Val : Natural) return Ctrl is begin return (Controlled with Val); end Make_Ctrl; end Types; -- main.adb with Ada.Text_IO; use Ada.Text_IO; with Types; use Types; procedure Main is procedure Ensure_Value (Val : Natural; Exp : Natural; Act : Natural) is begin if Val /= Exp then Put_Line ("ERROR: transient finalized too early"); Put_Line (" Expected:" & Exp'Img); Put_Line (" Got :" & Act'Img); end if; end Ensure_Value; function Factorial (Val : Natural) return Natural is begin if Val > 1 then return Factorial (Val - 1) * Val; else return 1; end if; end Factorial; Exp : constant Natural := 9876; Junk : constant Natural := 5432; Flag : Boolean; Val : Natural; begin Put_Line ("Case expression"); Val := (case Factorial (3) is when 6 => Make_Ctrl (Exp).Data, when others => Junk); Ensure_Value (Val, Exp, Junk); Put_Line ("If expression"); Val := (if Factorial (3) = 6 then Make_Ctrl (Exp).Data else Junk); Ensure_Value (Val, Exp, Junk); Put_Line ("If statement"); if Val = Exp and then Make_Ctrl (Exp).Data = Exp then Put_Line (" then statements"); end if; Put_Line ("Assignment"); Flag := Val = Exp and then Make_Ctrl (Exp).Data = Exp; Put_Line ("End"); end Main; ---------------------------- -- Compilation and output -- ---------------------------- $ gnatmake -q -gnat12 main.adb $ ./main Case expression fin fin If expression fin fin If statement fin fin then statements Assignment fin fin End Tested on x86_64-pc-linux-gnu, committed on trunk 2013-02-06 Hristian Kirtchev * exp_ch4.adb (Expand_N_Expression_With_Actions): Rewritten. This routine should be able to properly detect controlled transient objects in its actions and generate the appropriate finalization actions. * exp_ch6.adb (Enclosing_Context): Removed. (Expand_Ctrl_Function_Call): Remove local subprogram and constant. Use routine Within_Case_Or_If_Expression to determine whether the lifetime of the function result must be extended to match that of the context. * exp_util.ads, exp_util.adb (Within_Case_Or_If_Expression): New routine. Index: exp_util.adb =================================================================== --- exp_util.adb (revision 195784) +++ exp_util.adb (working copy) @@ -7944,6 +7944,43 @@ end if; end Type_May_Have_Bit_Aligned_Components; + ---------------------------------- + -- Within_Case_Or_If_Expression -- + ---------------------------------- + + function Within_Case_Or_If_Expression (N : Node_Id) return Boolean is + Par : Node_Id; + + begin + -- Locate an enclosing case or if expression. Note that these constructs + -- appear as expression_with_actions, hence the test using the original + -- node. + + Par := N; + while Present (Par) loop + if Nkind_In (Original_Node (Par), N_Case_Expression, + N_If_Expression) + then + return True; + + -- Prevent the search from going too far + + elsif Nkind_In (Par, N_Entry_Body, + N_Package_Body, + N_Package_Declaration, + N_Protected_Body, + N_Subprogram_Body, + N_Task_Body) + then + return False; + end if; + + Par := Parent (Par); + end loop; + + return False; + end Within_Case_Or_If_Expression; + ---------------------------- -- Wrap_Cleanup_Procedure -- ---------------------------- Index: exp_util.ads =================================================================== --- exp_util.ads (revision 195784) +++ exp_util.ads (working copy) @@ -6,7 +6,7 @@ -- -- -- S p e c -- -- -- --- Copyright (C) 1992-2012, Free Software Foundation, Inc. -- +-- Copyright (C) 1992-2013, Free Software Foundation, Inc. -- -- -- -- GNAT is free software; you can redistribute it and/or modify it under -- -- terms of the GNU General Public License as published by the Free Soft- -- @@ -818,6 +818,9 @@ -- is conservative, in that a result of False is decisive. A result of True -- means that such a component may or may not be present. + function Within_Case_Or_If_Expression (N : Node_Id) return Boolean; + -- Determine whether arbitrary node N is within a case or an if expression + procedure Wrap_Cleanup_Procedure (N : Node_Id); -- Given an N_Subprogram_Body node, this procedure adds an Abort_Defer call -- at the start of the statement sequence, and an Abort_Undefer call at the Index: exp_ch4.adb =================================================================== --- exp_ch4.adb (revision 195784) +++ exp_ch4.adb (working copy) @@ -6,7 +6,7 @@ -- -- -- B o d y -- -- -- --- Copyright (C) 1992-2012, Free Software Foundation, Inc. -- +-- Copyright (C) 1992-2013, Free Software Foundation, Inc. -- -- -- -- GNAT is free software; you can redistribute it and/or modify it under -- -- terms of the GNU General Public License as published by the Free Soft- -- @@ -4984,145 +4984,317 @@ -------------------------------------- procedure Expand_N_Expression_With_Actions (N : Node_Id) is + In_Case_Or_If_Expression : constant Boolean := + Within_Case_Or_If_Expression (N); - procedure Process_Transient_Object (Decl : Node_Id); - -- Given the declaration of a controlled transient declared inside the - -- Actions list of an Expression_With_Actions, generate all necessary - -- types and hooks in order to properly finalize the transient. This - -- mechanism works in conjunction with Build_Finalizer. + function Process_Action (Act : Node_Id) return Traverse_Result; + -- Inspect and process a single action of an expression_with_actions - ------------------------------ - -- Process_Transient_Object -- - ------------------------------ + -------------------- + -- Process_Action -- + -------------------- - procedure Process_Transient_Object (Decl : Node_Id) is + function Process_Action (Act : Node_Id) return Traverse_Result is + procedure Process_Transient_Object (Obj_Decl : Node_Id); + -- Obj_Decl denotes the declaration of a transient controlled object. + -- Generate all necessary types and hooks to properly finalize the + -- result when the enclosing context is elaborated/evaluated. - function Find_Insertion_Node return Node_Id; - -- Complex conditions in if statements may be converted into nested - -- EWAs. In this case, any generated code must be inserted before the - -- if statement to ensure proper visibility of the hook objects. This - -- routine returns the top most short circuit operator or the parent - -- of the EWA if no nesting was detected. + ------------------------------ + -- Process_Transient_Object -- + ------------------------------ - ------------------------- - -- Find_Insertion_Node -- - ------------------------- + procedure Process_Transient_Object (Obj_Decl : Node_Id) is + function Find_Enclosing_Context return Node_Id; + -- Find the context where the expression_with_actions appears - function Find_Insertion_Node return Node_Id is - Par : Node_Id; + ---------------------------- + -- Find_Enclosing_Context -- + ---------------------------- + function Find_Enclosing_Context return Node_Id is + function Is_Body_Or_Unit (N : Node_Id) return Boolean; + -- Determine whether N denotes a body or unit declaration + + --------------------- + -- Is_Body_Or_Unit -- + --------------------- + + function Is_Body_Or_Unit (N : Node_Id) return Boolean is + begin + return Nkind_In (N, N_Entry_Body, + N_Package_Body, + N_Package_Declaration, + N_Protected_Body, + N_Subprogram_Body, + N_Task_Body); + end Is_Body_Or_Unit; + + -- Local variables + + Par : Node_Id; + Top : Node_Id; + + -- Start of processing for Find_Enclosing_Context + + begin + -- The expression_with_action is in a case or if expression and + -- the lifetime of any temporary controlled object is therefore + -- extended. Find a suitable insertion node by locating the top + -- most case or if expressions. + + if In_Case_Or_If_Expression then + Par := N; + Top := N; + while Present (Par) loop + if Nkind_In (Original_Node (Par), N_Case_Expression, + N_If_Expression) + then + Top := Par; + + -- Prevent the search from going too far + + elsif Is_Body_Or_Unit (Par) then + exit; + end if; + + Par := Parent (Par); + end loop; + + -- The topmost case or if expression is now recovered, but + -- it may still not be the correct place to add all the + -- generated code. Climb to find a parent that is part of a + -- declarative or statement list. + + Par := Top; + while Present (Par) loop + if Is_List_Member (Par) + and then + not Nkind_In (Par, N_Component_Association, + N_Discriminant_Association, + N_Parameter_Association, + N_Pragma_Argument_Association) + then + return Par; + + -- Prevent the search from going too far + + elsif Is_Body_Or_Unit (Par) then + exit; + end if; + + Par := Parent (Par); + end loop; + + return Par; + + -- Shor circuit operators in complex expressions are converted + -- into expression_with_actions. + + else + -- Take care of the case where the expression_with_actions + -- is burried deep inside an if statement. The temporary + -- function result must be finalized before the then, elsif + -- or else statements are evaluated. + + -- if Something + -- and then Ctrl_Func_Call + -- then + -- + -- + -- end if; + + -- To achieve this, find the topmost logical operator. The + -- generated actions are then inserted before/after it. + + Par := N; + while Present (Par) loop + + -- Keep climbing past various operators + + if Nkind (Parent (Par)) in N_Op + or else Nkind_In (Parent (Par), N_And_Then, N_Or_Else) + then + Par := Parent (Par); + else + exit; + end if; + end loop; + + Top := Par; + + -- The expression_with_action might be located in a pragm + -- in which case locate the pragma itself: + + -- pragma Precondition (... and then Ctrl_Func_Call ...); + + -- Similar case occurs when the expression_with_actions is + -- related to an object declaration or assignment: + + -- Obj [: Some_Typ] := ... and then Ctrl_Func_Call ...; + + while Present (Par) loop + if Nkind_In (Par, N_Assignment_Statement, + N_Object_Declaration, + N_Pragma) + then + return Par; + + elsif Is_Body_Or_Unit (Par) then + exit; + end if; + + Par := Parent (Par); + end loop; + + -- Return the topmost short circuit operator + + return Top; + end if; + end Find_Enclosing_Context; + + -- Local variables + + Context : constant Node_Id := Find_Enclosing_Context; + Loc : constant Source_Ptr := Sloc (Obj_Decl); + Obj_Id : constant Entity_Id := Defining_Identifier (Obj_Decl); + Obj_Typ : constant Node_Id := Etype (Obj_Id); + Desig_Typ : Entity_Id; + Expr : Node_Id; + Ptr_Id : Entity_Id; + Temp_Id : Entity_Id; + + -- Start of processing for Process_Transient_Object + begin - -- Climb up the branches of a complex condition + -- Step 1: Create the access type which provides a reference to + -- the transient object. - Par := N; - while Nkind_In (Parent (Par), N_And_Then, N_Op_Not, N_Or_Else) loop - Par := Parent (Par); - end loop; + if Is_Access_Type (Obj_Typ) then + Desig_Typ := Directly_Designated_Type (Obj_Typ); + else + Desig_Typ := Obj_Typ; + end if; - return Par; - end Find_Insertion_Node; + -- Generate: + -- Ann : access [all] ; - -- Local variables + Ptr_Id := Make_Temporary (Loc, 'A'); - Ins_Node : constant Node_Id := Find_Insertion_Node; - Loc : constant Source_Ptr := Sloc (Decl); - Obj_Id : constant Entity_Id := Defining_Identifier (Decl); - Obj_Typ : constant Entity_Id := Etype (Obj_Id); - Desig_Typ : Entity_Id; - Expr : Node_Id; - Ptr_Decl : Node_Id; - Ptr_Id : Entity_Id; - Temp_Decl : Node_Id; - Temp_Id : Node_Id; + Insert_Action (Context, + Make_Full_Type_Declaration (Loc, + Defining_Identifier => Ptr_Id, + Type_Definition => + Make_Access_To_Object_Definition (Loc, + All_Present => + Ekind (Obj_Typ) = E_General_Access_Type, + Subtype_Indication => New_Reference_To (Desig_Typ, Loc)))); - -- Start of processing for Process_Transient_Object + -- Step 2: Create a temporary which acts as a hook to the + -- transient object. Generate: - begin - -- Step 1: Create the access type which provides a reference to the - -- transient object. + -- Temp : Ptr_Id := null; - if Is_Access_Type (Obj_Typ) then - Desig_Typ := Directly_Designated_Type (Obj_Typ); - else - Desig_Typ := Obj_Typ; - end if; + Temp_Id := Make_Temporary (Loc, 'T'); - -- Generate: - -- Ann : access [all] ; + Insert_Action (Context, + Make_Object_Declaration (Loc, + Defining_Identifier => Temp_Id, + Object_Definition => New_Reference_To (Ptr_Id, Loc))); - Ptr_Id := Make_Temporary (Loc, 'A'); + -- Mark this temporary as created for the purposes of exporting + -- the transient declaration out of the Actions list. This signals + -- the machinery in Build_Finalizer to recognize this special + -- case. - Ptr_Decl := - Make_Full_Type_Declaration (Loc, - Defining_Identifier => Ptr_Id, - Type_Definition => - Make_Access_To_Object_Definition (Loc, - All_Present => - Ekind (Obj_Typ) = E_General_Access_Type, - Subtype_Indication => New_Reference_To (Desig_Typ, Loc))); + Set_Status_Flag_Or_Transient_Decl (Temp_Id, Obj_Decl); - Insert_Action (Ins_Node, Ptr_Decl); - Analyze (Ptr_Decl); + -- Step 3: Hook the transient object to the temporary - -- Step 2: Create a temporary which acts as a hook to the transient - -- object. Generate: + if Is_Access_Type (Obj_Typ) then + Expr := Convert_To (Ptr_Id, New_Reference_To (Obj_Id, Loc)); + else + Expr := + Make_Attribute_Reference (Loc, + Prefix => New_Reference_To (Obj_Id, Loc), + Attribute_Name => Name_Unrestricted_Access); + end if; - -- Temp : Ptr_Id := null; + -- Generate: + -- Temp := Ptr_Id (Obj_Id); + -- + -- Temp := Obj_Id'Unrestricted_Access; - Temp_Id := Make_Temporary (Loc, 'T'); + Insert_After_And_Analyze (Obj_Decl, + Make_Assignment_Statement (Loc, + Name => New_Reference_To (Temp_Id, Loc), + Expression => Expr)); - Temp_Decl := - Make_Object_Declaration (Loc, - Defining_Identifier => Temp_Id, - Object_Definition => New_Reference_To (Ptr_Id, Loc)); + -- Step 4: Finalize the function result after the context has been + -- evaluated/elaborated. Generate: - Insert_Action (Ins_Node, Temp_Decl); - Analyze (Temp_Decl); + -- if Temp /= null then + -- [Deep_]Finalize (Temp.all); + -- Temp := null; + -- end if; - -- Mark this temporary as created for the purposes of exporting the - -- transient declaration out of the Actions list. This signals the - -- machinery in Build_Finalizer to recognize this special case. + Insert_Action_After (Context, + Make_If_Statement (Loc, + Condition => + Make_Op_Ne (Loc, + Left_Opnd => New_Reference_To (Temp_Id, Loc), + Right_Opnd => Make_Null (Loc)), - Set_Status_Flag_Or_Transient_Decl (Temp_Id, Decl); + Then_Statements => New_List ( + Make_Final_Call + (Obj_Ref => + Make_Explicit_Dereference (Loc, + Prefix => New_Reference_To (Temp_Id, Loc)), + Typ => Desig_Typ), - -- Step 3: Hook the transient object to the temporary + Make_Assignment_Statement (Loc, + Name => New_Reference_To (Temp_Id, Loc), + Expression => Make_Null (Loc))))); + end Process_Transient_Object; - if Is_Access_Type (Obj_Typ) then - Expr := Convert_To (Ptr_Id, New_Reference_To (Obj_Id, Loc)); - else - Expr := - Make_Attribute_Reference (Loc, - Prefix => New_Reference_To (Obj_Id, Loc), - Attribute_Name => Name_Unrestricted_Access); + -- Start of processing for Process_Action + + begin + if Nkind (Act) = N_Object_Declaration + and then Is_Finalizable_Transient (Act, N) + then + Process_Transient_Object (Act); + + -- Avoid processing temporary function results multiple times when + -- dealing with nested expression_with_actions. + + elsif Nkind (Act) = N_Expression_With_Actions then + return Abandon; + + -- Do not process temporary function results in loops. This is + -- done by Expand_N_Loop_Statement and Build_Finalizer. + + elsif Nkind (Act) = N_Loop_Statement then + return Abandon; end if; - -- Generate: - -- Temp := Ptr_Id (Obj_Id); - -- - -- Temp := Obj_Id'Unrestricted_Access; + return OK; + end Process_Action; - Insert_After_And_Analyze (Decl, - Make_Assignment_Statement (Loc, - Name => New_Reference_To (Temp_Id, Loc), - Expression => Expr)); - end Process_Transient_Object; + procedure Process_Single_Action is new Traverse_Proc (Process_Action); -- Local variables - Decl : Node_Id; + Act : Node_Id; -- Start of processing for Expand_N_Expression_With_Actions begin - Decl := First (Actions (N)); - while Present (Decl) loop - if Nkind (Decl) = N_Object_Declaration - and then Is_Finalizable_Transient (Decl, N) - then - Process_Transient_Object (Decl); - end if; + Act := First (Actions (N)); + while Present (Act) loop + Process_Single_Action (Act); - Next (Decl); + Next (Act); end loop; end Expand_N_Expression_With_Actions; Index: exp_ch6.adb =================================================================== --- exp_ch6.adb (revision 195784) +++ exp_ch6.adb (working copy) @@ -6,7 +6,7 @@ -- -- -- B o d y -- -- -- --- Copyright (C) 1992-2012, Free Software Foundation, Inc. -- +-- Copyright (C) 1992-2013, Free Software Foundation, Inc. -- -- -- -- GNAT is free software; you can redistribute it and/or modify it under -- -- terms of the GNU General Public License as published by the Free Soft- -- @@ -4036,45 +4036,6 @@ ------------------------------- procedure Expand_Ctrl_Function_Call (N : Node_Id) is - function Enclosing_Context return Node_Id; - -- Find the enclosing context where the function call appears - - ----------------------- - -- Enclosing_Context -- - ----------------------- - - function Enclosing_Context return Node_Id is - Context : Node_Id; - - begin - Context := Parent (N); - while Present (Context) loop - - -- The following could use a comment (and why is N_Case_Expression - -- not treated in a similar manner ??? - - if Nkind (Context) = N_If_Expression then - exit; - - -- Stop the search when reaching any statement because we have - -- gone too far up the tree. - - elsif Nkind (Context) = N_Procedure_Call_Statement - or else Nkind (Context) in N_Statement_Other_Than_Procedure_Call - then - exit; - end if; - - Context := Parent (Context); - end loop; - - return Context; - end Enclosing_Context; - - -- Local variables - - Context : constant Node_Id := Enclosing_Context; - begin -- Optimization, if the returned value (which is on the sec-stack) is -- returned again, no need to copy/readjust/finalize, we can just pass @@ -4096,15 +4057,12 @@ Remove_Side_Effects (N); - -- The function call is part of an if expression dependent expression. - -- The temporary result must live as long as the if expression itself, - -- otherwise it will be finalized too early. Mark the transient as - -- processed to avoid untimely finalization. + -- When the temporary function result appears inside a case or an if + -- expression, its lifetime must be extended to match that of the + -- context. If not, the function result would be finalized prematurely + -- and the evaluation of the expression could yield the wrong result. - -- Why no special handling for case expressions here ??? - - if Present (Context) - and then Nkind (Context) = N_If_Expression + if Within_Case_Or_If_Expression (N) and then Nkind (N) = N_Explicit_Dereference then Set_Is_Processed_Transient (Entity (Prefix (N)));