HomeForumSourceResearchGuide
Sign in to contribute to source. how it works
Component util.compiler.StoreAnalyser by barry
expand copy to clipboardexpand
uses HWI

data StoreVarEx {
	StoreVarEx links[]
	int index
	char name[]
	char intfName[]
}

data StoreFunctionEx {
	StoreVarEx locals[]
	OpToken instructions[]
	char name[]
	char intfName[]
}

const bool DEBUG = false
const bool RETURN_AUTO_STORE = true

component provides StoreAnalyser requires io.Output out, data.IntUtil iu, DNCUtil dncUtil, data.adt.List {

	bool isAutoStoreType(DanaType t, DanaType types[])
		{
		if (t == null)
			{
			return false
			}
		
		if (t.class == DanaType.INTEGER || t.class == DanaType.DECIMAL)
			{
			return true
			}
		
		if (t.class == DanaType.ARRAY)
			{
			DanaType fieldType = dncUtil.findType(types, t.fields[0].type)
			
			return isAutoStoreType(fieldType, types)
			}
		
		return false
		}
	
	DanaType getConstuctor(DanaType object, DanaType types[])
		{
		for (int i = 0; i < object.fields.arrayLength; i ++)
			{
			DanaType fieldType = dncUtil.findType(types, object.fields[i].type)

			if (fieldType.class == DanaType.FUNCTION && object.fields[i].name == object.name)
				{
				return fieldType
				}
			}
		
		return null
		}

	StoreFunction getStoreFunction(StoreObject object, char name[], char intfName[])
		{
		for (int i = 0; i < object.functions.arrayLength; i++)
			{
			if (object.functions[i].name == name)
				return object.functions[i]
			}

		return null
		}
	
	StoreFunctionEx getStoreFunctionEx(StoreFunctionEx exfs[], char name[], char intfName[])
		{
		for (int i = 0; i < exfs.arrayLength; i++)
			{
			if (exfs[i].name == name)
				return exfs[i]
			}

		return null
		}

	bool isLocalVariableAccess(OpToken t)
		{
		if (t.type == HWI.OP_ID_GET_REF && (t.parameters[1].type == HWI.OP_ID_LOCALS))
			{
			return true
			}
			else if (t.type == HWI.OP_ID_GET_PTR_HND && (t.parameters[1].type == HWI.OP_ID_LOCALS))
			{
			return true
			}
		
		return false
		}
	
	bool isGlobalVariableAccess(OpToken t)
		{
		if (t.type == HWI.OP_ID_GET_REF && (t.parameters[1].type == HWI.OP_ID_GLOBALS || t.parameters[1].type == HWI.OP_ID_PRIVATE_IVS || t.parameters[1].type == HWI.OP_ID_PRIVATE_INTER_IVS))
			{
			return true
			}
			else if (t.type == HWI.OP_ID_GET_PTR_HND && (t.parameters[1].type == HWI.OP_ID_GLOBALS || t.parameters[1].type == HWI.OP_ID_PRIVATE_IVS || t.parameters[1].type == HWI.OP_ID_PRIVATE_INTER_IVS))
			{
			return true
			}
		
		return false
		}

	OpToken StoreAnalyser:getOriginLocal(OpToken t, DanaType types[])
		{
		//recurse down through "t" to the single instruction
		while (t != null && !isLocalVariableAccess(t))
			{
			if (t.parameters.arrayLength == 0)
				{
				return null
				}
			
			//filter out anything that's nested within something which returns an auto-store type
			if (isAutoStoreType(t.returnType, types))
				{
				return null
				}
			
			if (t.type == HWI.OP_ID_MEMBER_ACCESS
				|| t.type == HWI.OP_ID_GET_INDEX_TO_ASSIGN
				|| t.type == HWI.OP_ID_GET_FIELD_TO_ASSIGN
				|| t.type == HWI.OP_ID_GET_PTR_HND
				|| t.type == HWI.OP_ID_GET_INDEX_R
				|| t.type == HWI.OP_ID_GET_INDEX_P
				|| t.type == HWI.OP_ID_GET_REF)
				{
				t = t.parameters[1]
				}
				else
				{
				t = t.parameters[0]
				}
			}

		return t
		}
	
	OpToken getOriginGlobal(OpToken t, DanaType types[])
		{
		//recurse down through "t" to the single instruction
		while (t != null && !isGlobalVariableAccess(t))
			{
			if (t.parameters.arrayLength == 0)
				{
				return null
				}
			
			//filter out anything that's nested within something which returns an auto-store type
			if (isAutoStoreType(t.returnType, types))
				{
				return null
				}
			
			if (t.type == HWI.OP_ID_MEMBER_ACCESS
				|| t.type == HWI.OP_ID_GET_INDEX_TO_ASSIGN
				|| t.type == HWI.OP_ID_GET_FIELD_TO_ASSIGN
				|| t.type == HWI.OP_ID_GET_PTR_HND
				|| t.type == HWI.OP_ID_GET_INDEX_R
				|| t.type == HWI.OP_ID_GET_INDEX_P
				|| t.type == HWI.OP_ID_GET_REF)
				{
				t = t.parameters[1]
				}
				else
				{
				t = t.parameters[0]
				}
			}

		return t
		}
	
	int getParamAssignIndex(OpToken call, int index)
		{
		OpToken pq = call.parameters[index]

		OpToken fp = pq.parameters[0]

		if (pq.type == HWI.OP_ID_ASSIGN_POINTER)
			fp = pq.parameters[1]
		
		return fp.variableIndex
		}
	
	OpToken[] getVariableAccesses(OpToken t, DanaType types[])
		{
		//recursive down through "t", collecting every variable access
		// - we only count those which are not primitive arrays
		OpToken result[] = null

		if (t != null)
			{
			if (t.parameters.arrayLength == 0)
				{
				return result
				}
			
			//filter out anything that's nested within something which returns an auto-store type
			if (isAutoStoreType(t.returnType, types))
				{
				return null
				}
			
			//filter out anything that's nested within a function call, which in v272 are treated as having always store-able result values
			// - this is a messy approach prone to mistakes, but the correction requires a more sophisticated algorithm and additional language features
			if (RETURN_AUTO_STORE)
				{
				if (t.type == HWI.OP_ID_STACK_FRAME_LAUNCH_P || t.type == HWI.OP_ID_STACK_FRAME_LAUNCH_R)
					{
					return null
					}
				}
			
			//filter instructions which return unrelated things
			if (t.type == HWI.OP_ID_TYPEOF_VAR || t.type == HWI.OP_ID_NEW_DYN_OBJECT_CONSTRUCT || t.type == HWI.OP_ID_NEW_DYN_OBJECT)
				{
				return null
				}
			
			if (isLocalVariableAccess(t))
				{
				result = new OpToken[](result, t)
				}
			
			//in object constructors, ignore anything related to a parameter that isn't qualified as "store"
			if (t.type == HWI.OP_ID_NEW_OBJECT_CONSTRUCT_CHK)
				{
				DanaType objectType = t.parameters[0].returnType
				DanaType cfunction = getConstuctor(objectType, types)

				for (int i = 1; i < t.parameters.arrayLength; i++)
					{
					//the the actual parameter index being assigned in to, which isn't necessarily the in-order index
					int fpIndex = getParamAssignIndex(t, i)
					if (cfunction.fields[fpIndex].qualifier == "store" || cfunction.fields[fpIndex].qualifier == "opt.store")
						{
						result = new OpToken[](result, getVariableAccesses(t.parameters[i], types))
						}
					}
				}
				//otherwise consider everything
				else
				{
				for (int i = 0; i < t.parameters.arrayLength; i++)
					{
					result = new OpToken[](result, getVariableAccesses(t.parameters[i], types))
					}
				}
			}

		return result
		}
	
	bool hasLocalAC(StoreFunction f, OpToken varsRHS[])
		{
		for (int j = 0; j < varsRHS.arrayLength; j++)
			{
			if (f.locals[varsRHS[j].variableIndex-1].class == StoreVar.AC_LOCAL)
				return true
			}
		
		return false
		}
	
	//TODO: the "stack depth" checks here should not really be required, we should be able to avoid creating cycles in the first place during link graph construction
	char[] makeCausePath(StoreObject object, StoreFunction f, int index, StoreFunctionEx exfs[], StoreVarEx exl[], int depth)
		{
		StoreVar sv = f.locals[index]
		StoreVarEx svx = exl[index]
		if (sv.regraded)
			{
			for (int i = 0; i < svx.links.arrayLength; i++)
				{
				if (svx.links[i].name == f.name)
					{
					if (f.locals[svx.links[i].index].class == StoreVar.AC_LOCAL)
						{
						if (depth < 50)
							{
							return "variable '$(sv.name)' is potentially assigned from '$(f.locals[svx.links[i].index].name)'; $(makeCausePath(object, f, svx.links[i].index, exfs, exl, depth+1))"
							}
							else
							{
							return "variable '$(sv.name)' is potentially assigned from '$(f.locals[svx.links[i].index].name)'; further cause chain not followed due to potentially recursive stack"
							}
						}
					}
					else
					{
					StoreFunction nextf = getStoreFunction(object, svx.links[i].name, svx.links[i].intfName)
					StoreFunctionEx exf = getStoreFunctionEx(exfs, svx.links[i].name, svx.links[i].intfName)

					if (nextf.locals[svx.links[i].index].class == StoreVar.AC_LOCAL)
						{
						if (depth < 50)
							{
							return "variable '$(sv.name)' is potentially assigned from '$(svx.links[i].name)():$(nextf.locals[svx.links[i].index].name)'; $(makeCausePath(object, nextf, svx.links[i].index, exfs, exf.locals, depth+1))"
							}
							else
							{
							return "variable '$(sv.name)' is potentially assigned from '$(svx.links[i].name)():$(nextf.locals[svx.links[i].index].name)'; further cause chain not followed due to potentially recursive stack"
							}
						}
					}
				}
			
			if (svx.links.arrayLength != 0)
				{
				return "no additional information from $(svx.links.arrayLength) links, link0 was $(svx.links[0].name) of $(f.locals[svx.links[0].index].name) ($(svx.links[0].index))"
				}
				else
				{
				return "no additional information from $(svx.links.arrayLength) links"
				}
			}
			else
			{
			return "variable '$(sv.name)' is defined as local-only"
			}
		
		return null
		}
	
	char[] getLocalCauses(StoreObject object, StoreFunction f, OpToken varsRHS[], StoreFunctionEx exfs[], StoreVarEx exl[])
		{
		for (int j = 0; j < varsRHS.arrayLength; j++)
			{
			if (f.locals[varsRHS[j].variableIndex-1].class == StoreVar.AC_LOCAL)
				{
				return makeCausePath(object, f, varsRHS[j].variableIndex-1, exfs, exl, 0)
				}
			}
		
		return null
		}
	
	void printLocalCauses(StoreObject object, StoreFunction f, OpToken varsRHS[], StoreFunctionEx exfs[], StoreVarEx exl[])
		{
		for (int j = 0; j < varsRHS.arrayLength; j++)
			{
			if (f.locals[varsRHS[j].variableIndex-1].class == StoreVar.AC_LOCAL)
				{
				char p[] = makeCausePath(object, f, varsRHS[j].variableIndex-1, exfs, exl, 0)
				out.println(p)
				}
			}
		}
	
	void addError(StoreAnalysis result, char file[], int lineNumber, char string[])
		{
		for (int i = 0; i < result.warnings.arrayLength; i++)
			{
			if (result.warnings[i].line == lineNumber && result.warnings[i].message == string)
				return
			}
		
		result.warnings = new OpParseError[](result.warnings, new OpParseError(file, lineNumber, string))
		}
	
	char[] getFunctionName(DanaType t, int functionIndex, DanaType types[])
		{
		int j = 0
		for (int i = 0; i < t.fields.arrayLength; i++)
			{
			DanaType fieldType = dncUtil.findType(types, t.fields[i].type)
			if (fieldType.class == DanaType.FUNCTION)
				{
				if (j == functionIndex)
					{
					return t.fields[i].name
					}
				
				j ++
				}
			}
		
		return null
		}
	
	DanaType getFunctionType(DanaType t, int functionIndex, DanaType types[])
		{
		int j = 0
		for (int i = 0; i < t.fields.arrayLength; i++)
			{
			DanaType fieldType = dncUtil.findType(types, t.fields[i].type)
			if (fieldType.class == DanaType.FUNCTION)
				{
				if (j == functionIndex)
					{
					return fieldType
					}
				j ++
				}
			}
		
		return null
		}
	
	bool linkExists(StoreVarEx svx, StoreVarEx link)
		{
		if (svx === link) return true

		for (int i = 0; i < svx.links.arrayLength; i++)
			{
			if (svx.links[i] === link) return true

			for (int j = 0; j < link.links.arrayLength; j++)
				{
				if (svx === link.links[j] || svx.links[i] === link.links[j]) return true
				}
			}
		
		return false
		}
	
	bool addLinks(OpToken originLHS, StoreVarEx svx, StoreFunction f, OpToken varsRHS[], StoreVarEx exl[])
		{
		for (int j = 0; j < varsRHS.arrayLength; j++)
			{
			if (DEBUG) out.println(" -- RHS involves $(varsRHS[j].variableIndex)")
			if (!linkExists(svx, exl[varsRHS[j].variableIndex-1])) svx.links = new StoreVarEx[](svx.links, exl[varsRHS[j].variableIndex-1])

			if (f.locals[originLHS.variableIndex-1].class == StoreVar.AC_STORE && f.locals[varsRHS[j].variableIndex-1].class == StoreVar.AC_LOCAL)
				{
				if (DEBUG) out.println(" -- LHS forced to AC-LOCAL")
				f.locals[originLHS.variableIndex-1].class = StoreVar.AC_LOCAL
				f.locals[originLHS.variableIndex-1].regraded = true
				return true
				}
				else if (f.locals[originLHS.variableIndex-1].class == StoreVar.AC_LOCAL)
				{
				if (DEBUG) out.println(" -- LHS already AC-LOCAL")
				}
			}
		
		return false
		}
	
	bool isStoreParam(DanaType object, int functionIndex, OpToken paramToken, DanaType types[])
		{
		//get the param index from the param access instruction
		OpToken fp = paramToken.parameters[0]
		if (paramToken.type == HWI.OP_ID_ASSIGN_POINTER)
			fp = paramToken.parameters[1]

		int paramIndex = fp.variableIndex

		DanaType type = getFunctionType(object, functionIndex, types)

		DanaTypeField tf = type.fields[paramIndex]

		return tf.qualifier == "store" || tf.qualifier == "opt,store"
		}

	//this function broadly acts on three things: assignments to variables, parameters passed to local function calls, and parameters passed to object function calls
	bool build(char file[], StoreObject object, StoreFunction f, StoreFunctionEx exfs[], StoreVarEx exl[], OpToken instructions[], OpToken parent, DanaType types[], StoreAnalysis result)
		{
		bool change = false
		for (int i = 0; i < instructions.arrayLength; i++)
			{
			if (build(file, object, f, exfs, exl, instructions[i].parameters, instructions[i], types, result)) change = true

			if (instructions[i].type == HWI.OP_ID_ASSIGN_POINTER || instructions[i].type == HWI.OP_ID_VASSIGN)
				{
				//get the origin of the LHS
				//get the origin(s) of the RHS (for which there may be multiple)
				//do the association, considering transitivity, and consider switch to local-only for LHS if necessary
				// - we need a way to list associations on variables; maybe we pass StoreFunctionEx in and it has an array of StoreVarEx's which we can place copies
				OpToken originLHS = getOriginLocal(instructions[i].parameters[1], types)
				OpToken varsRHS[] = getVariableAccesses(instructions[i].parameters[2], types)

				if (originLHS != null)
					{
					StoreVarEx lhs = exl[originLHS.variableIndex-1]
					if (DEBUG) out.println("line $(originLHS.lineNumber):: LHS assign variable index $(originLHS.variableIndex)")

					//now perform the association of variables in the StoreVarEx, and modify the StoreVar to local if necessary (reporting change if so)
					for (int j = 0; j < varsRHS.arrayLength; j++)
						{
						if (DEBUG) out.println(" -- RHS involves $(varsRHS[j].variableIndex)")
						if (!linkExists(lhs, exl[varsRHS[j].variableIndex-1])) lhs.links = new StoreVarEx[](lhs.links, exl[varsRHS[j].variableIndex-1])

						if (f.locals[originLHS.variableIndex-1].class == StoreVar.AC_STORE && f.locals[varsRHS[j].variableIndex-1].class == StoreVar.AC_LOCAL)
							{
							if (DEBUG) out.println(" -- LHS forced to AC-LOCAL")
							f.locals[originLHS.variableIndex-1].class = StoreVar.AC_LOCAL
							f.locals[originLHS.variableIndex-1].regraded = true
							change = true
							}
							else if (f.locals[originLHS.variableIndex-1].class == StoreVar.AC_LOCAL)
							{
							if (DEBUG) out.println(" -- LHS already AC-LOCAL")
							}
						}
					}
					else if (parent != null && (parent.type == HWI.OP_ID_STACK_FRAME_LAUNCH_P || parent.type == HWI.OP_ID_STACK_FRAME_LAUNCH_R))
					{
					//param1 is always the stack frame variable
					originLHS = instructions[i].parameters[1]

					if (DEBUG) out.println("line $(originLHS.lineNumber):: CALL assign parameter index $(originLHS.variableIndex), parent1 is $(parent.parameters[1].type)")
					
					if (parent.parameters[1].type == HWI.OP_ID_STACK_FRAME_INIT_LOCAL)
						{
						//this doesn't allow us to directly track cause and effect, so here we need to grab the Ex of the other object, via its StoreFunctionEx ...
						StoreVar lhs = null
						StoreVarEx lhsEx = null
						
						// - local function parameters can be forced to local (they all start as store)
						StoreFunction sfq = getStoreFunction(object, parent.parameters[1].functionRef.name, parent.parameters[1].functionRef.intfName)
						StoreFunctionEx sfqx = null//getStoreFunctionEx(exfs, parent.parameters[1].functionRef.name, parent.parameters[1].functionRef.intfName)
						for (int m = 0; m < exfs.arrayLength; m++)
							{
							if (exfs[m].name == parent.parameters[1].functionRef.name)
								{
								sfqx = exfs[m]
								break
								}
							}
						lhs = sfq.locals[originLHS.variableIndex-1]
						lhsEx = sfqx.locals[originLHS.variableIndex-1]

						for (int j = 0; j < varsRHS.arrayLength; j++)
							{
							if (DEBUG) out.println(" -- RHS involves $(varsRHS[j].variableIndex)")
							if (!linkExists(lhsEx, exl[varsRHS[j].variableIndex-1])) lhsEx.links = new StoreVarEx[](lhsEx.links, exl[varsRHS[j].variableIndex-1])

							if (lhs.class == StoreVar.AC_STORE && f.locals[varsRHS[j].variableIndex-1].class == StoreVar.AC_LOCAL)
								{
								if (DEBUG) out.println(" -- LHS forced to AC-LOCAL")
								lhs.class = StoreVar.AC_LOCAL
								lhs.regraded = true
								change = true
								}
								else if (lhs.class == StoreVar.AC_LOCAL)
								{
								if (DEBUG) out.println(" -- LHS already AC-LOCAL")
								}
							}
						}
						else if (parent.parameters[1].type == HWI.OP_ID_STACK_FRAME_INIT_OBJECT)
						{
						//it's a function call on an object
						// - if it's a local going into a store, that's an error, UNLESS the object ref is ALSO local, in which case it's a LOAD-time register flag adjustment
						// - there IS a second constraint, which is that you can't pass things into store parameters of an object you don't own (this is the implementation of read-only for non-owners, for object state), but this constraint is checked at runtime (though it would be useful to add warnings when we're sure it's an issue here)

						originLHS = getOriginLocal(parent.parameters[1].parameters[0], types)

						if (originLHS != null)
							{
							if (DEBUG) out.println("OBJ-CALL-L, type $(originLHS.returnType.name), index $(parent.parameters[1].functionIndex)")
							if (f.locals[originLHS.variableIndex-1].class == StoreVar.AC_LOCAL)
								{
								//this is the case where we're allowed to send otherwise local-only values into store parameters
								if (DEBUG) out.println(" -- object origin already AC-LOCAL")
								}
								else
								{
								//this is an error, if the parameter has a store qualifier, and the varRHS list has AC_LOCAL values
								StoreVarEx lhs = exl[originLHS.variableIndex-1]
								addLinks(originLHS, lhs, f, varsRHS, exl)

								if (isStoreParam(parent.parameters[1].parameters[0].returnType, parent.parameters[1].functionIndex, instructions[i], types) && hasLocalAC(f, varsRHS))
									{
									//here we force the originLHS to AC_LOCAL, and later pick up any residual errors resulting from assignment of that originLHS into store locations
									if (DEBUG) out.println(" -- object origin forced to AC-LOCAL")
									f.locals[originLHS.variableIndex-1].class = StoreVar.AC_LOCAL
									f.locals[originLHS.variableIndex-1].regraded = true
									change = true
									}
								}
							}
							else if ((originLHS = getOriginGlobal(parent.parameters[1].parameters[0], types)) != null)
							{
							if (DEBUG) out.println("OBJ-CALL-G, type $(originLHS.returnType.name), index $(parent.parameters[1].functionIndex)")
							//this is an error, if the parameter has a store qualifier, and the varRHS list has AC_LOCAL values

							if (isStoreParam(parent.parameters[1].parameters[0].returnType, parent.parameters[1].functionIndex, instructions[i], types) && hasLocalAC(f, varsRHS))
								{
								if (DEBUG) out.println(" -- ERROR: assignment into store parameter of function $(originLHS.returnType.name):$(getFunctionName(originLHS.returnType, parent.parameters[1].functionIndex, types)) from a local-only variable")
								if (DEBUG) printLocalCauses(object, f, varsRHS, exfs, exl)

								addError(result, file, instructions[i].lineNumber, "assignment into store parameter of function $(originLHS.returnType.name):$(getFunctionName(originLHS.returnType, parent.parameters[1].functionIndex, types)) from a local-only variable: $(getLocalCauses(object, f, varsRHS, exfs, exl))")
								}
							}
							else
							{
							//derivation of the object reference via a function call (?) -- TBD on whether or not we should act on this in some way
							}
						}
					}
					else if ((originLHS = getOriginGlobal(instructions[i].parameters[1], types)) != null)
					{
					//this is only for error generation, reporting cause and effect...
					if (hasLocalAC(f, varsRHS))
						{
						if (DEBUG) out.println(" -- ERROR: assignment into global state from a local-only variable on line $(instructions[i].lineNumber)")
						if (DEBUG) printLocalCauses(object, f, varsRHS, exfs, exl)
						addError(result, file, instructions[i].lineNumber, "assignment into global state from a local-only variable: $(getLocalCauses(object, f, varsRHS, exfs, exl))")
						}
					}
				}
			}
		
		return change
		}

	bool storeBuild(char file[], StoreObject object, StoreFunction f, StoreFunctionEx exfs[], StoreFunctionEx exf, DanaType types[], StoreAnalysis result)
		{
		//keep looping until there are no more store --> local changes
		// - we only care about origin variables in assignments, not fields or array indices
		// - we're just tracking interactions between origin variables
		// - on right-hand-sides we need to be aware of constructor operations which collect multiple origin variables together (array, data, and objects)
		bool change = true
		bool changed = false

		while (change)
			{
			change = build(file, object, f, exfs, exf.locals, exf.instructions, null, types, result)
			if (change) changed = true
			}
		
		return changed
		}

	//this procedure analyses all functions to determine the local/store status of each of their local variables
	// - including their parameters, in the case of local functions
	StoreAnalysis StoreAnalyser:analyse(char file[], ProvidedObject objects[], DanaType types[])
		{
		//start with the interface functions on each object, since the local/store status of their parameters is fixed
		// - local variables that aren't parameters start as "store" and are downgraded to "local" if they are assigned to from any local variable throughout the function
		// - we record every assignment, so we have a reason for the downgrade
		// - we should ALSO look at assignments into globals, so we can error if it's from a local, and report why that thing is a local if it's indirect
		// - with this figured out, we can move onto local functions, whose parameters will already have started as "store" and been forced-to-local if needed by the above process

		//we first create a structure which mirrors objects[]: with objects, functions, and variables
		StoreAnalysis result = new StoreAnalysis()
		StoreFunctionEx streams[]

		result.objects = new StoreObject[objects.arrayLength]

		for (int i = 0; i < objects.arrayLength; i++)
			{
			result.objects[i] = new StoreObject()

			for (int j = 0; j < objects[i].interfaces.arrayLength; j++)
				{
				for (int k = 0; k < objects[i].interfaces[j].functions.arrayLength; k++)
					{
					StoreFunction nsf = new StoreFunction(objects[i].interfaces[j].functions[k].name, objects[i].mainInterface, objects[i].interfaces[j].type)
					//nsf.instructions = objects[i].interfaces[j].functions[k].instructions //TODO: fails to make "result" into a local-only variable, which is ironic...
					result.objects[i].functions = new StoreFunction[](result.objects[i].functions, nsf)

					//the compiler includes the return value as a "variable", so we need one less than that...
					nsf.locals = new StoreVar[objects[i].interfaces[j].functions[k].variables.fields.arrayLength-1]

					StoreFunctionEx exsf = new StoreFunctionEx(new StoreVarEx[nsf.locals.arrayLength], objects[i].interfaces[j].functions[k].instructions, objects[i].interfaces[j].functions[k].name, objects[i].interfaces[j].type)
					streams = new StoreFunctionEx[](streams, exsf)

					int vn = 0
					for (int m = 1; m < objects[i].interfaces[j].functions[k].variables.fields.arrayLength; m++)
						{
						byte class = StoreVar.AC_STORE
						OpFunction opf = objects[i].interfaces[j].functions[k]
						if (vn < opf.parameters.arrayLength && (opf.parameters[vn].qualifier & Parameter.Q_STORE) != Parameter.Q_STORE)
							{
							class = StoreVar.AC_LOCAL
							}
						if (DEBUG) out.println("var $m class is $(class) ($(opf.parameters.arrayLength) params) for $(nsf.name)")
						nsf.locals[vn] = new StoreVar(opf.variables.fields[m].name, opf.variables.fields[m].type, m, class)
						exsf.locals[vn] = new StoreVarEx(null, vn, objects[i].interfaces[j].functions[k].name, objects[i].interfaces[j].type)
						vn ++
						}
					}
				}
			
			for (int j = 0; j < objects[i].localFunctions.arrayLength; j++)
				{
				StoreFunction nsf = new StoreFunction(objects[i].localFunctions[j].name)
				//nsf.instructions = objects[i].localFunctions[j].instructions
				result.objects[i].functions = new StoreFunction[](result.objects[i].functions, nsf)

				nsf.locals = new StoreVar[objects[i].localFunctions[j].variables.fields.arrayLength-1]

				StoreFunctionEx exsf = new StoreFunctionEx(new StoreVarEx[nsf.locals.arrayLength], objects[i].localFunctions[j].instructions, objects[i].localFunctions[j].name)
				streams = new StoreFunctionEx[](streams, exsf)

				int vn = 0
				for (int m = 1; m < objects[i].localFunctions[j].variables.fields.arrayLength; m++)
					{
					byte class = StoreVar.AC_STORE
					OpFunction opf = objects[i].localFunctions[j]
					//if (vn < opf.parameters.arrayLength && (opf.parameters[vn].qualifier & Parameter.Q_STORE) != Parameter.Q_STORE)
					//	{
					//	class = StoreVar.AC_LOCAL
					//	}
					if (DEBUG) out.println("var $m class is $(class) ($(opf.parameters.arrayLength) params) for $(nsf.name)")
					nsf.locals[vn] = new StoreVar(opf.variables.fields[m].name, opf.variables.fields[m].type, m, class)
					exsf.locals[vn] = new StoreVarEx(null, vn, objects[i].localFunctions[j].name)
					vn ++
					}
				}
			}
		
		int si = 0
		for (int i = 0; i < result.objects.arrayLength; i++)
			{
			//interface-functions first: we only need to do these once at this outer level as their parameter qualifiers are fixed (though we may run multiple passes for internal changes)
			int ssi = si
			for (int j = 0; j < result.objects[i].functions.arrayLength; j++)
				{
				if (result.objects[i].functions[j].implName != null)
					{
					if (DEBUG) out.println("== store build for $(result.objects[i].functions[j].implName):$(result.objects[i].functions[j].intfName):$(result.objects[i].functions[j].name) ==")
					storeBuild(file, result.objects[i], result.objects[i].functions[j], streams, streams[si], types, result)
					}
				si ++
				}
			
			//local-functions next: we may need to do these multiple times once at this outer level, since their parameter qualifiers may change
			bool change = true
			while (change)
				{
				change = false
				si = ssi
				for (int j = 0; j < result.objects[i].functions.arrayLength; j++)
					{
					if (result.objects[i].functions[j].implName == null)
						{
						if (DEBUG) out.println("== store build for _local:$(result.objects[i].functions[j].name) ==")
						bool c = storeBuild(file, result.objects[i], result.objects[i].functions[j], streams, streams[si], types, result)
						if (c) change = true
						}
					si ++
					}
				}
			}

		return result
		}
	
	}
Revision history
To propose a new revision to this entity, use dana source put -uc your/new/version.dn -n util.compiler.StoreAnalyser -m "reason for update" -u yourUsername
Version 1 (this version) by barry
Notes for this version: Full redesign of how 'store' is conceptually modelled, with far greater compiler guidance on causes and effects.