Compilation with debug-print option will show how the compiling process proceeds and how a source program is transformed into intermediate representation (HIR and LIR). It is the shortest way of understanding how the compiler infrastructure works and understanding HIR, LIR, and symbol table that are used as interfaces between compiler components.
Please try to compile several short programs specifying debug-print option in such way as
java -classpath ./classes coins.driver.Driver -S -coins:trace=HIR.1/LIR.1/Sym.1 sample.c(See 2. How to use the Compiler Driver and 3. How to use C Compiler.)
LIR is an abstract representation of machine language program. In LIR, operations are decomposed into elementary operations such as SET, JUMP, CALL, etc. with simple or compound operands. The operands may represent memory, register, or expression. Data type of LIR corresponds to data type handled in machines such as integer, float of some bit length.
LIR is a language that can represents not only operations but also the entire program providing features to describe a module (a collection of functions), memory area, and data. Semantics of LIR is defined rigorously according to the denotational semantics to avoid misunderstanding.
In order to make a compiler based on the COINS infrastructure or to modify a compiler based on the infrastructure, it is necessary to read
Usage of methods is usually described in upper interfaces so that it is not necessary to read lower interfaces. Methods are interrelated and there may be restrictions in invoking them. In upper interfaces, many upper methods are provided to make the use of access methods simple. You will misuse the access methods if you read lower interfaces or read implementation modules before reading upper interfaces.
There are several classes that contains global information and methods available to all over the compiler infrastructure. They are placed in "coins" package. Some of them are
*cfront -- C parser converting C to AST (abstract syntax tree for C) *ast -- Classes to generate AST for C *expr -- AST expression generation *stmnt -- AST statement generation *ffront -- Fortran77 parser translating Fortran77 to HIR coins -- Infrastructure of the COINS compiler driver -- Compiler driver *casttohir -- C AST to HIR translator ir -- Intermediate representation IR hir -- High level intermediate representation HIR lir -- Obsolete sym -- Symbol table hir2lir -- HIR to old LIR converter aflow -- Control flow and data flow analyzer for HIR flow -- Obsolete alias -- Alias analyzer opt -- Basic optimizer ssa -- SSA form optimizer for LIR lparallel -- loop parallelizer mdf -- Coarse grain parallelizing module (SMP parallelizer) simd -- SIMD parallelizer snapshot -- Make snap shot file (XML file) for the visualizer back-end -- Back-end ana -- Control flow analyzer for LIR cfg -- Control flow graph builder for LIR **gen -- Code generator and target machine descriptions other than SPARC and x86 lir -- LIR manager opt -- Backend optimizer regalo -- Register allocator sched -- Instruction scheduler sym -- LIR symbol manager **targets -- Machine parameter table interface **tmd -- Target machine description (SPARC and x86) util -- Backend utility hir2c -- HIR to C translator lir2c -- LIR to C translator
To change the sequence of component invocations and to add, replace, delete some compiler components, Driver.java may be changed. It would be better to make a subclass of Driver.java and override some of Driver's methods appropriately. See, for example, http://www.coins-project.org/international/advanceduse/compilerDevelopE.html.
The infrastructure does not use static fields except for "static final" in order to make it possible to develop a compiler where its components can be executed in concurrent. All methods except for some ones in Root classes (classes such as IoRoot, SymRoot, HirRoot, LirRoot, etc.) are non-static methods and should be applied to instances.
The compiler driver instantiates IoRoot first and supplies source file, object file, print file, etc. (See getSourceFile(), printOut, objectFile, msgOut, etc. in IoRoot.java). All compiler components should convey the instance of IoRoot (ioRoot) directly or indirectly and make it protected or public so that it can be accessed directly or indirectly from methods in the component.
In more detail, objects of other Root classes include a reference to the instance of IoRoot in order to enable input/output operations. All Sym objects include a reference to SymRoot object, all HIR objects include a reference to HirRoot, and so on. In this way, almost all classes has a link to the IoRoot directly or indirectly in order to enable input/output operations in their methods. IoRoot has such methods as
getSourceFile(), getSourceFilePath(), getCompileSpecification()to access files and compile specifications given by command line.
As the next step, the compiler driver instantiates SymRoot to make symbol information be shared between compiler components. All Sym objects such as symbol table and entries in the symbol table (variable, subprogram, constant, type, etc.) contain a reference to the SymRoot object so that methods of Sym class and IoRoot class can be invoked. The symbol tables are nested reflecting scope of symbols and organized into tree structure.
The root of the symbol tables is named as symTableRoot. The symbol table currently effective is called current symbol table and named as symTableCurrent. They are accessed from SymRoot object. Built-in symbols representing basic types, etc. are registered in symTableRoot and can be accessed from SymRoot object, hence they can be accessed from all methods under Sym and its subclasses.
The compiler driver instantiates HirRoot and then invokes some parser such as C parser that translates source program into HIR. The parser should convey the instance of HirRoot to its components so that they can access I/O files, symbol tables, and HIR information. The super class of HirRoot is IrRoot where the root of intermediate representation (IR) of input source program is recorded as programRoot. The IrRoot is also the super class of HirRoot. HIR representation of input program can be traced starting from programRoot.
The compiler driver may either parse all subprograms in a source file before code generation, or repeat parsing and code generation for each subprogram in the source file. In the former case, inter-procedural optimization and parallelization may be possible but consumes large memory space. In the later case, required memory space is relatively small but the possibility of inter-procedural optimization is limited.
Error messages and warning messages are issued by invoking put method of Message class in "coins" package. The number of messages issued is counted for each group of messages. Compiler implementers may prepare their own error handlers that invokes the put method in order to provide some information peculiar to each component such as source program line number. (See Message.java.)
It is often required to see the status of compiler for debugging. The method
void print(int pLevel, String pAt, String pMessage)in the class Debug in coins package prints pAt and pMessage if pLevel is less or equal to the debug level specified by command line. Its usage is illustrated by
hirRoot.ioRoot.dbgHir.print(4, "subpNode", pSubp.getName());(See Debug.java.)
coins.ir.IrList lSubpDefList = ((Program)hirRoot.programRoot).getSubpDefinitionList(); Iterator lSubpDefIterator = lSubpDefList.iterator(); while (lSubpDefIterator.hasNext()) { SubpDefinition lSubpDef = (SubpDefinition)(lSubpDefIterator.next()); .... }where, hirRoot refers to the HirRoot object.
Subp lSubp = lSubpDef.getSubpSym();The symbol table local to the subprogram is get by
SymTable lSymTable = lSubp.getSymTable();or
SymTable lSymTable = lSubpDef.getSymTable();The procedural body of the subprogram is get by
HIR lHirSubpBody = lSubp.getHirBody();or
HIR lHirSubpBody = lSubpDef.getHirBody();(See IrLisr of coins.ir, SubpDefinition, HirIterator of coins.ir.hir, Subp of coins.sym)
Every HIR nodes of the subprogram lSubp can be traversed by using HirIterator in such a way as
for (HirIterator lHirIterator = hirRoot.hir.hirIterator(lSubp.getHirBody()); lHirIterator.hasNext(); ) { HIR lNode = lHirIterator.next(); .... }All statements in the subprogram can be traversed by a coding sequence such as
for (HirIterator lHirIterator = hirRoot.hir.hirIterator(lSubp.getHirBody()); lHirIterator.hasNextStmt(); ) { Stmt lStmt = lHirIterator.getNextStmt(); .... }Note that some node or statement may be null and it is better to do null-check before applying methods to them.
To catch node or statement of some particular class during the traversing procedure, such coding as
if (lNode instanceof VarNode) { .... } if (lNode instanceof SubpNode) { .... } if (lStmt instanceof AssignStmt) { .... }will be convenient. They may be also caught by such coding as
if (lStmt.getOperator() == HIR.OP_VAR) { .... }Another way of coding is to use HirVisitor in such a way as
public class ProcessHirNode extends coins.ir.hir.HirVisitorModel1 { public final HirRoot hirRoot; public ProcessSymNode( HirRoot pHirRoot ) { super(pHirRoot); hirRoot = pHirRoot; } public void processSymNode( Subp pSubp ) { hirRoot.symRoot.subpCurrent = pSubp; visit(pSubp.getHirBody()); } protected void atVarNode( VarNode pVarNode ) { .... } protected void atSubpNode( SubpNode pSubpNode ) { .... } .... }(See HIR, HirVisitor, HirVisitorModel1, HirVisitorModel2 in coins.ir.hir.)
To scan all symbols recorded in symbol tables, iterators are provided in SymTable interface. A coding sequence
for (SymIterator lIterator = lSymTable.getSymIterator(); lIterator.hasNext(); ) { Sym lSym = lIterator.next(); ..... }traverses all symbols recorded in the symbol table lSymTable. If SymIterator is applied to symTableRoot, all global symbols in the given program unit are traversed.
Another coding sequence
for (SymNestIterator lIterator = lSymTable.getSymIterator(); lIterator.hasNext(); ) { Sym lSym = lIterator.next(); ..... }traverses all symbols recorded in the symbol table lSymTable and its descendent symbol tables. If SymNestIterator is applied to symTableCurrentSubp, all symbols local to the current subprogram are traversed. If SymNestIterator is applied to symTableRoot, all symbols recorded in the given program unit except constants in symTableConst are traversed.
The next coding sequence
for (SymTableIterator lTableIterator = lSymTable.getSymTableIterator(); lTableIterator.hasNext(); ) { SymTable lSymTableCurr = lTableIterator.next(); for (SymIterator lSymIterator = lSymTableCurr.getSymIterator(); lSymIterator.hasNext(); ) { Sym lSym = lSymIterator.next(); ...... } }will traverse all symbol tables under lSymTable and all symbols in the traversed symbol tables examining attributes of the traversed symbol tables.
Subp lSubp = symRoot.sym.defineSubp("sub1".intern(), symRoot.sym.typeInt);where, the first parameter specifies subprogram name and the second parameter specifies return value type. symRoot refers to SymRoot object. If symRoot is not accessible directly but hirRoot is accessible, replace symRoot in the above coding by hirRoot.symRoot. String parameters for Sym, HIR methods should have .intern() in order to make unique String object that can be compared by "==" operator instead of the "equals" method. All String objects returned by Sym, HIR methods are unique String object and need not to have .intern(). (See Sym0.java, Sym.java, HIR0.java, HIR.java.)
Similarly, a variable symbol can be constructed by
Var lVar = symRoot.defineVar("var1".intern(), symRoot.typeFloat);Integer constant, long int constant can be made by
IntConst lIntConst1 = symRoot.sym.intConst(123, symRoot.typeInt); IntConst lLongConst1= symRoot.sym.intConst(123, symRoot.typeLong); IntConst lIntConst2 = symRoot.sym.intConst("123".intern(), symRoot.typeInt);and floating constant can be made by
FloatConst lPai = symRoot.sym.floatConst(3.14, symRoot.typeFloat); FloatConst lDoubleConst1 = symRoot.sym.floatConst(1.2, symRoot.typeDouble);For mode detail, see the Sym interface (Sym0.java and Sym.java).
Care should be taken in making a string constant because the representation of character string differs by language. For example, a string constant in C has trailing "\0" and may contain preceding escape character for some special characters. A string constant is recorded as a pure string (processing escape characters by makeStringBody of coins.SourceLanguage) that is language independent. To make a string for C language from the pure string, makeCstring method is provided, for Java language, makeJavaString method is provided, and so on.
Several symbol tables are constructed according to the structure of given source program. At first, a global symbol table is created by initiate() of SymRoot and symbols inherent to the COINS infrastructure are recorded in it. The symbols inherent to the COINS compiler infrastructure are such ones as basic types and bool constants. Types of each source language are mapped to the corresponding types of the COINS compiler infrastructure in such way as
C int COINS int C array COINS vector Fortran INTEGER COINS int Fortran REAL COINS floatWhen a new scope of symbols is opened, a new symbol table is to be created and linked to ancestor symbol table that contains symbols to be inherited by the new scope (pushSymTable()). When the current scope is closed, the current symbol table is to be closed by which the ancestor symbol table becomes the current symbol table again (by using popSymTable()).
Symbols are searched in the current symbol table (symTableCurrent of SymRoot) and its ancestors in the reverse order of scope addition. The methods pushSymTable and popSymTable changes symTableCurrent when they are called. Popped symbol table is not discarded unless it is empty but made invisible for search procedures so as to make inter-procedure optimization and parallelization can be done. A symbol table usually has corresponding program construct such as subprogram and it is called as the owner of the symbol table. There are links between such constructs (owner) and corresponding symbol table to show their correspondence (getOwner). Anonymous construct (anonymous Struct, BlockStmt, etc.) may have name generated by the compiler.
Symbols may have indication of scope (extern, public, private, compile_unit, etc.) and variables may have indication of storage class (static, automatic, etc.). In storage allocation and symbol treatment in code generation, these indications and nesting of symbol tables should be properly treated. Care should be taken that one subprogram may have nested symbol tables. Nesting of subprograms is treated as the nesting of corresponding symbol tables.
symTableRoot // Root of SymTable. symTableConst // Constant table. symTableUnique // SymTable that contains generated unique name. symTableCurrent // Referes to the symbol table for subprogram, // etc. under construction or under processing. symTableCurrentSubp // Symbol table of current subprogram. Some kinds of // symbols (Type, Label, tmporal variable, etc.) are // registered not in symTableCurrent // but in symTableCurrentSubp.The subprogram under construction or processing is recorded in subpCurrent of SymRoot.
In parsing, flow analysis, optimization, code generation, etc., it is strongly recommended to set SymTableCurrent, subpCurrent, symTableCurrentSubp as it is exemplified in SimpleMain.java.
They are used in searching/generating symbols. If new symbols are to be created in such processing, SymTableCurrent and subpCurrent should be set properly. Several methods such as sym/pushSymTable, sym/popSymTable, aflow/subpFlow keeps such consistency automatically as it is described in explanations of these methods.
pushSymTalbe/popSymTable methods should be used in parsers but should not be used in optimization, code generation, etc. because pushSymTable creates a new SymTable corresponding to a new scope in input source program.
SymRoot contains type symbols of base types such as typeBool, typeChar, typeInt, etc. as predefined symbols.
Base type (type intrinsic to HIR): int represented by typeInt of SymRoot float represented by typeFloat of SymRoot .... (see SymRoot) Introduced type (type introduced by the input program): pointer type represented by the class PointerType vector type represented by the class VectorType structure type represented by the class StructType union type represented by the class UnionType enumeration type represented by the class EnumType subprogram type represented by the class SubpType defined type represented by the class DefinedTypeA pointer type is defined by pointer indication (* in C) and the type of the target of the pointer.
Type symbols are created by factory methods in Sym interface. The factory methods for type creation are baseType, pointerType, vectorType, structType, unionType, enumType, subpType and definedType.
The structure of SubpType, StructType, UnionType, and EnumType are a little complicated. It is not recommended to use subpType directly but it is recommended to use defineSubp of Sym interface that defines both subprogram symbol and subprogram type. For making type instance of StructType, UnionType, or EnumType, read carefully the explanation of the corresponding method structType, unionType, or enumType of Sym interface.
In order to define a subprogram symbol,
make the subprogram symbol by defineSubp(...), add formal parameters by addParam(....), close the subprogram declaration by closeSubpHeader(....)in such a way as
Subp lSubp = symRoot.sym.defineSubp("name".intern(), returnType); SymTable lSubpSymTable = symRoot.symTableCurrent.pushSymTable(lSubp); lSubp.addParam(param1); lSubp.addParam(param2); .... lSubp.setOptionalParam(); // Not required if it has no optional parameter. lSubp.closeSubpHeader(); Var lVar1 = lSubpSymTable.defineVar("a".intern(), symRoot.typeInt); .... symRoot.symTableCurrent.popSymTable();Above procedure will make a subprogram object with inevitable fields such as parameter list, return value type, and subprogram type.
<SUBP < paramType_1 paramType_2 ... > returnValueType optionalParam >where, paramType_1, paramType_2, ... are parameter type, returnValueType is return value type, optionalParam is true or false depending on whether optional parameter ("..." in C) is specified or not.
To make a structure type, structType method is provided in Sym interface. Users may understand how to use it by following example: As for
struct listNode { int nodeValue; struct listNode *next; } listAnchor, listNode1;following coding will make corresponding StructType.
Sym lTag = symRoot.symTableCurrent.generateTag("listNode".intern()); StructType lListStruct = sym.structType(null, lTag); // Incomplete type. PointerType lListPtrType = sym.pointerType(lListStruct); PointerType lIntPtrType = sym.pointerType(symRoot.typeInt); symRoot.symTableCurrent.pushSymTable(lListStruct); Elem lValue = sym.defineElem("nodeValue".intern(), symRoot.typeInt); Elem lNext = sym.defineElem("next".intern(), lListPtrType); lListStruct.addElem(lValue); lListStruct.addElem(lNext); lListStruct.finishStructType(true); symRoot.symTableCurrent.popSymTable();Methods are provided to get information of introduced types:
getSizeValue of Sym interface getPointedValue of Sym interface getElemCount of VectorType interface getElemList of StructType interface getParameterTypeList of SubpType interface ....
public Var generateVar( Type pType );is provided in the SymTable interface to generate a temporal variable in the symbol table local to the current subprogram (symTableCurrentSubp).
In order to generate labels, a method
public Label generateLabel();is provided in the SymTable interface. It generates a label in the symbol table local to the current subprogram (symTableCurrentSubp).
Most of HIR constructs have correspondence to some source language constructs, e.g.
SubpDefinition - subprogram definition Stmt - statement AssignStmt - assign statement LoopStmt - loop statement BlockStmt - block statement Exp - expression VarNode - variable ConstNode - constantSubcomponent of HIR constructs can be get by methods provided in each HIR subclass (interface that extends HIR). For example,
getLeftSide(), getRightSide() of AssignStmt getIfCondition(), getThenPart(), getElsePart() of IfStmt getLoopStartCondition(), getLoopBodyPart() of ForStmt, WhileStmt, UntilStmt that extend LoopStmt getExp1(), getExp2() of Exp getSymNodeSym() of VarNode, ElemNode, ConstNode that extend SymNodeAs for detail, see corresponding interfaces that extend HIR.
The subcomponents can be get also by specifying child number by getChild1(), getChild2(), and getChild(int pChildNumber). In such coding, exact knowledge of HIR data structure is required. getChildCount of IR interface gives the number of children of HIR nodes. (getChild1() and getChild2() have less overhead than getChild(1) and getChild(2).)
All HIR nodes have type attribute. It can be get by the method getType. Some HIR nodes may have flags set during parsing, analysis, etc. The method getFlag(int pFlagNumber) returns the status of the flag indicated by pFlagNumber.
The method print(....) of HIR prints the subtree stemming from the specified node, that is, all subtrees of the specified node are printed recursively.
A Symbol node can be generated by factory methods of HIR.
VarNode lVarNode1 = hirRoot.hir.varNode(lVar1); SubpNode lSubpNode1 = hirRoot.hir.subpNode(lSubp); ConstNode lConstNode1 = hirRoot.hir.constNode(lIntConst1);will instantiate VarNode, SubpNode, ConstNode, each respectively. hirRoot.hir, hirRoot.symRoot.sym, etc. may be shortened by local declarations
HIR hir = hirRoot.hir; Sym sym = hirRoot.symRoot.sym;Arithmetic expressions can be built by such coding as
Exp lExp1 = hir.exp(HIR.OP_ADD, lVarNode1, lConstNode1); Exp lExp2 = hir.exp(HIR.OP_MULT, lExp1, hir.varNode(lVar2));Assign-statement, if-statement, etc. are built by
Stmt lAssign1 = hir.assignStmt(lVarNode1, lExp1); Stmt lAssign2 = hir.assignStmt(lVarNode1, lExp2); Stmt lIf1 = hir.ifStmt(hir.exp(HIR.OP_CMP_EQ, lExp1, hir.constNode(0, symRoot.typeInt)), lAssign1, lAssign2);etc.
setChild1( IR pChild1 ), setChild2( IR pChild2 ), setChild( int pChildNumber, IR pChild )of IR interface are available for such construction. The top down construction requires knowledge of detailed structure of HIR tree. Recommended way is bottom up construction by using the prepared factory methods.
In some cases, strict bottom up construction is difficult. For example, in the construction of block statement and subprogram definition, most of their children are not known at first. Several methods are provided to construct such subtrees. They are explained in the next section.
BlockStmt lBlockStmt = hir.blockStmt(null); lBlockStmt.addLastStmt(lAssign1); lBlockStmt.addLastStmt(lIf1); ....(See HIR, BlockStmt in coins.ir.hir.)
Subprogram can be constructed by such statement sequence as
Subp lMain = symRoot.sym.defineSubp("main".intern(), symRoot.typeInt); SymTable lSymTable = symRoot.symTableRoot.pushSymTable(lMain); lMain.closeSubpHeader(); SubpDefinition lMainDef = hir.subpDefinition(lMain, lSymTable); BlockStmt lBlockStmt = hir.blockStmt(null); lMainDef.setHirBody(lBlockStmt); .... lBlockStmt.addLastStmt(lAssign1); .... symRoot.symTableCurrent.popSymTable();(In case of prototype declaration, use closeSubpPrototype instead of closeSubpHeader. See HIR, SubpDefinition, Subp, SimpleMain, etc.)
IrList, HirList can be constructed by such statement sequence as
HirList lList = hir.irList(); lList.add(....); ....(See HIR in coins.ir.hir, IrList in coins.ir.)
It is possible to build Sym objects and HIR objects by invoking constructors of VarImpl, SubpImpl, VarNodeImpl, ConstNodeImpl, AssignStmtImpl, IfStmtImpl, etc. but such coding is not recommended. Such coding may cause many errors because there are some hidden parameters supplied by factory methods and there are some preparatory methods to be applied to parameters.
It should be noted that the structure of HIR is tree. Every nodes in the HIR tree should be created newly and should not be shared because sharing of nodes violates the data structure rule of tree. If a subtree same as some subtree X is required, X should be copied by the method
X.copyWithOperands()if X is an expression or
X.copyWithOperandsChangingLabels()if X is a statement that may include label definitions. The HIR method "isTree" checks whether the rule of tree structure is violated or not. The method "isTree" is invoked automatically if debug-print level of HIR is greater than 1 in Driver.java.
When entire HIR subtree of a subprogram is constructed, finishHir() should be called in such way as
lSubpDefinition.finishHir();where lSubpDefinition represents SubpDefinition node of the subprogram. The method finishHir() does such operations as giving index number to HIR nodes under the subtree, checking tree structure conformance, certificating getHirPosition() for labels, and so on. When the HIR subtree of a subprogram was changed in optimization and parallelization, then finishHir() should also be called for the subtree. This method is not required to call for each modification of statements ans expressions of SubpDefinition but at the end of creation or transformation of the entire SubpDefinition subtree.
test/c/testdriver.shis available to do automatic test for the C test program suite. It can automatically compile and execute the programs in the test program suite and compare the results with the corresponding expected results. Following is an example of command sequence to do such automatic test.
cd ./classes -- Type at the root of the COINS compiler infrastructure. ../test/c/testdriver.sh -v ../test/c/*/*.c ../test/c/testdriver.sh -v ../test/c/*/*/*.cThe first and the second invocations of "testdriver.sh" will do test by using test programs under the first level and the second level sub-directories, respectively. The result of the test will be shown by files which will be located under
testdriver-yymmdd-hhmmssthat is a directory created during the test.
../test/c/testdriver.sh -v ../test/c/*/*.cthen make a temporal directory such as "test/c2" and move several sub-directories such as TestFnami2, WhiteBox1, WhiteBox2 (that contain many files) to the temporal directory and execute
../test/c/testdriver.sh -v ../test/c2/*/*.cas additional command.
if ! java coins.driver.Driver -coins:${TARGET}preprocessor=\ 'cpp -I../lang/c/include' -S -o $CCCOUT $i &>$CCCLOGin "testdriver.sh". You can change compile option by changing this line in such way as
if ! java coins.driver.Driver -coins:${TARGET},hirOpt=cse,preprocessor=\ 'cpp -I../lang/c/include' -S -o $CCCOUT $i &>$CCCLOGYou can do automatic test by using test programs under some sub-directory only. For example, if you want to do test by using test programs under Loop sub-directory only, give following command:
../test/c/testdriver.sh -v ../test/c/Loop/*.cSome test programs under sub-directories "test/c/unsupported" and "test/c-result-may-differ" will cause error but it may be normal because each of them may be a test program to cause error. Result of these test programs should be examined individually.
../test/c/testdriver.sh -v -t x86-cygwin ../test/c/Loop/*.cbut this command option is not yet reliable and in some environment, this may not correctly work. It is better to use the test script
test/c/testdriverw.shfor x86-cygwin.
cd ./classes ../test/c/testprepare.sh ../test/c/*/*.c ../test/c/testprepare.sh ../test/c/*/*/*.cWhen you added or modified test programs, you should also renew the expected results of the test programs added or modified.