mirror of
https://gitlab.com/camelot/kickc.git
synced 2024-06-03 07:29:37 +00:00
365 lines
19 KiB
Java
365 lines
19 KiB
Java
package dk.camelot64.kickc.passes;
|
|
|
|
import dk.camelot64.kickc.model.InternalError;
|
|
import dk.camelot64.kickc.model.*;
|
|
import dk.camelot64.kickc.model.iterator.ProgramValueIterator;
|
|
import dk.camelot64.kickc.model.statements.*;
|
|
import dk.camelot64.kickc.model.symbols.Procedure;
|
|
import dk.camelot64.kickc.model.symbols.StructDefinition;
|
|
import dk.camelot64.kickc.model.symbols.Variable;
|
|
import dk.camelot64.kickc.model.types.SymbolType;
|
|
import dk.camelot64.kickc.model.types.SymbolTypeInference;
|
|
import dk.camelot64.kickc.model.types.SymbolTypeStruct;
|
|
import dk.camelot64.kickc.model.values.*;
|
|
import dk.camelot64.kickc.passes.unwinding.ValueSource;
|
|
import dk.camelot64.kickc.passes.unwinding.ValueSourceFactory;
|
|
import dk.camelot64.kickc.passes.unwinding.ValueSourceParamValue;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.ListIterator;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
|
/** Convert all unwinding struct values into variables representing each member */
|
|
public class Pass1UnwindStructValues extends Pass1Base {
|
|
|
|
public Pass1UnwindStructValues(Program program) {
|
|
super(program);
|
|
}
|
|
|
|
@Override
|
|
public boolean step() {
|
|
if(getProgram().getStructVariableMemberUnwinding() == null) {
|
|
getProgram().setStructVariableMemberUnwinding(new StructVariableMemberUnwinding());
|
|
}
|
|
boolean modified = false;
|
|
// Unwind all procedure declaration parameters
|
|
modified |= unwindStructParameters();
|
|
// Unwind all usages of struct values
|
|
modified |= unwindStructReferences();
|
|
// Change all usages of members of struct values
|
|
modified |= unwindStructMemberReferences();
|
|
return modified;
|
|
}
|
|
|
|
/**
|
|
* Unwinds all usages of struct value references (in statements such as assignments.)
|
|
*/
|
|
private boolean unwindStructReferences() {
|
|
boolean modified = false;
|
|
for(var block : getGraph().getAllBlocks()) {
|
|
ListIterator<Statement> stmtIt = block.getStatements().listIterator();
|
|
while(stmtIt.hasNext()) {
|
|
Statement statement = stmtIt.next();
|
|
if(statement instanceof StatementAssignment) {
|
|
modified |= unwindAssignment((StatementAssignment) statement, stmtIt, block, getProgram());
|
|
} else if(statement instanceof StatementCall) {
|
|
modified |= unwindCall((StatementCall) statement, stmtIt, block);
|
|
} else if(statement instanceof StatementReturn) {
|
|
modified |= unwindReturn((StatementReturn) statement, stmtIt, block);
|
|
}
|
|
}
|
|
}
|
|
return modified;
|
|
}
|
|
|
|
/**
|
|
* Change all usages of members inside statements to the unwound member variables
|
|
*/
|
|
private boolean unwindStructMemberReferences() {
|
|
AtomicBoolean modified = new AtomicBoolean(false);
|
|
ProgramValueIterator.execute(
|
|
getProgram(), (programValue, currentStmt, stmtIt, currentBlock) -> {
|
|
if(programValue.get() instanceof StructMemberRef) {
|
|
StructMemberRef structMemberRef = (StructMemberRef) programValue.get();
|
|
final ValueSource structValueSource = ValueSourceFactory.getValueSource(structMemberRef.getStruct(), getProgram(), getProgramScope(), currentStmt, stmtIt, currentBlock);
|
|
if(structValueSource != null) {
|
|
final ValueSource memberUnwinding = structValueSource.getMemberUnwinding(structMemberRef.getMemberName(), getProgram(), getProgramScope(), currentStmt, stmtIt, currentBlock);
|
|
RValue memberSimpleValue = memberUnwinding.getSimpleValue(getProgramScope());
|
|
if(getLog().isVerboseStructUnwind())
|
|
getLog().append("Replacing struct member reference " + structMemberRef.toString(getProgram()) + " with member unwinding reference " + memberSimpleValue.toString(getProgram()));
|
|
programValue.set(memberSimpleValue);
|
|
modified.set(true);
|
|
}
|
|
}
|
|
});
|
|
return modified.get();
|
|
}
|
|
|
|
/**
|
|
* Unwind any call parameter that is a struct value into the member values
|
|
*
|
|
* @param call The call to unwind
|
|
*/
|
|
private boolean unwindCall(StatementCall call, ListIterator<Statement> stmtIt, Graph.Block currentBlock) {
|
|
final Procedure procedure = getProgramScope().getProcedure(call.getProcedure());
|
|
|
|
// Unwind struct value return value
|
|
|
|
boolean lvalUnwound = false;
|
|
Variable procReturnVar = procedure.getLocalVariable("return");
|
|
// TODO: Return-variable has been unwound - detect that instead - use getProgram().getStructVariableMemberUnwinding().getUnwindingMaster() like for parameters
|
|
if(procReturnVar != null && procReturnVar.isStructUnwind()) {
|
|
if(call.getlValue() != null && !(call.getlValue() instanceof ValueList)) {
|
|
// Return value already unwound - move on
|
|
final ValueSource valueSource = ValueSourceFactory.getValueSource(call.getlValue(), getProgram(), getProgramScope(), call, stmtIt, currentBlock);
|
|
RValue unwoundLValue = unwindValue(valueSource, call, stmtIt, currentBlock);
|
|
if(call.getlValue().equals(unwoundLValue))
|
|
throw new CompileError("Call return value already unwound", call);
|
|
if(unwoundLValue == null)
|
|
throw new CompileError("Cannot unwind call return value", call);
|
|
call.setlValue((LValue) unwoundLValue);
|
|
if(getLog().isVerboseStructUnwind())
|
|
getLog().append("Converted procedure call LValue to member unwinding " + call.toString(getProgram(), false));
|
|
lvalUnwound = true;
|
|
}
|
|
}
|
|
|
|
// Unwind any struct value parameters
|
|
ArrayList<RValue> unwoundParameters = new ArrayList<>();
|
|
boolean anyParameterUnwound = false;
|
|
final List<Variable> procParameters = procedure.getParameters();
|
|
final List<RValue> callParameters = call.getParameters();
|
|
if(callParameters != null && callParameters.size() > 0) {
|
|
for(int idx_call = 0, idx_proc = 0; idx_call < callParameters.size(); idx_call++) {
|
|
final RValue callParameter = callParameters.get(idx_call);
|
|
final Variable procParameter = procParameters.get(idx_proc);
|
|
boolean unwound = false;
|
|
final SymbolVariableRef unwindingMaster = getProgram().getStructVariableMemberUnwinding().getUnwindingMaster(procParameter.getRef());
|
|
if(unwindingMaster != null) {
|
|
// The procedure parameter is unwound
|
|
final ValueSource parameterSource = ValueSourceFactory.getValueSource(callParameter, getProgram(), getProgramScope(), call, stmtIt, currentBlock);
|
|
if(parameterSource != null && parameterSource.isUnwindable())
|
|
// Passing an unwinding struct value
|
|
for(String memberName : parameterSource.getMemberNames(getProgramScope())) {
|
|
ValueSource memberUnwinding = parameterSource.getMemberUnwinding(memberName, getProgram(), getProgramScope(), call, stmtIt, currentBlock);
|
|
unwoundParameters.add(memberUnwinding.getSimpleValue(getProgramScope()));
|
|
unwound = true;
|
|
anyParameterUnwound = true;
|
|
idx_proc++;
|
|
}
|
|
else
|
|
idx_proc++;
|
|
} else {
|
|
idx_proc++;
|
|
}
|
|
if(!unwound) {
|
|
unwoundParameters.add(callParameter);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(anyParameterUnwound) {
|
|
call.setParameters(unwoundParameters);
|
|
if(getLog().isVerboseStructUnwind())
|
|
getLog().append("Converted call struct value parameter to member unwinding " + call.toString(getProgram(), false));
|
|
}
|
|
return (anyParameterUnwound || lvalUnwound);
|
|
}
|
|
|
|
/**
|
|
* Unwind an LValue to a ValueList if it is unwindable.
|
|
*
|
|
* @param value The value to unwind
|
|
* @param statement The current statement
|
|
* @param stmtIt Statement iterator
|
|
* @param currentBlock current block
|
|
* @return The unwound ValueList. null if the value is not unwindable.
|
|
*/
|
|
private RValue unwindValue(ValueSource lValueSource, Statement statement, ListIterator<Statement> stmtIt, Graph.Block currentBlock) {
|
|
if(lValueSource == null) {
|
|
return null;
|
|
} else if(lValueSource.isSimple()) {
|
|
return lValueSource.getSimpleValue(getProgramScope());
|
|
} else if(lValueSource.isUnwindable()) {
|
|
ArrayList<RValue> unwoundMembers = new ArrayList<>();
|
|
for(String memberName : lValueSource.getMemberNames(getProgramScope())) {
|
|
ValueSource memberUnwinding = lValueSource.getMemberUnwinding(memberName, getProgram(), getProgramScope(), statement, stmtIt, currentBlock);
|
|
unwoundMembers.add(unwindValue(memberUnwinding, statement, stmtIt, currentBlock));
|
|
}
|
|
return new ValueList(unwoundMembers);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Unwind any return value that is a struct value into the member values
|
|
*
|
|
* @param statementReturn The return to unwind
|
|
*/
|
|
|
|
private boolean unwindReturn(StatementReturn statementReturn, ListIterator<Statement> stmtIt, Graph.Block currentBlock) {
|
|
final RValue returnValue = statementReturn.getValue();
|
|
if(returnValue == null)
|
|
return false;
|
|
if(returnValue instanceof SymbolVariableRef)
|
|
if(getProgramScope().getVar((SymbolVariableRef) returnValue).isStructClassic())
|
|
return false;
|
|
boolean unwound = false;
|
|
final ValueSource valueSource = ValueSourceFactory.getValueSource(returnValue, getProgram(), getProgramScope(), statementReturn, stmtIt, currentBlock);
|
|
RValue unwoundValue = unwindValue(valueSource, statementReturn, stmtIt, currentBlock);
|
|
if(unwoundValue != null && !returnValue.equals(unwoundValue)) {
|
|
statementReturn.setValue(unwoundValue);
|
|
if(getLog().isVerboseStructUnwind())
|
|
getLog().append("Converted procedure struct return value to member unwinding " + statementReturn.toString(getProgram(), false));
|
|
unwound = true;
|
|
}
|
|
return unwound;
|
|
}
|
|
|
|
/**
|
|
* Iterate through all procedures changing parameter lists by unwinding each struct value parameter to the unwound member variables
|
|
*/
|
|
private boolean unwindStructParameters() {
|
|
boolean modified = false;
|
|
// Iterate through all procedures changing parameter lists by unwinding each struct value parameter
|
|
for(Procedure procedure : getProgramScope().getAllProcedures(true)) {
|
|
ArrayList<String> unwoundParameterNames = new ArrayList<>();
|
|
boolean procedureUnwound = false;
|
|
for(Variable parameter : procedure.getParameters()) {
|
|
if(parameter.isStructUnwind()) {
|
|
StructVariableMemberUnwinding structVariableMemberUnwinding = getProgram().getStructVariableMemberUnwinding();
|
|
StructVariableMemberUnwinding.VariableUnwinding parameterUnwinding = structVariableMemberUnwinding.getVariableUnwinding(parameter.getRef());
|
|
for(String memberName : parameterUnwinding.getMemberNames()) {
|
|
final SymbolVariableRef memberUnwinding = parameterUnwinding.getMemberUnwound(memberName);
|
|
unwoundParameterNames.add(memberUnwinding.getLocalName());
|
|
procedureUnwound = true;
|
|
}
|
|
} else {
|
|
unwoundParameterNames.add(parameter.getLocalName());
|
|
}
|
|
}
|
|
if(procedureUnwound) {
|
|
procedure.setParameterNames(unwoundParameterNames);
|
|
if(getLog().isVerboseStructUnwind())
|
|
getLog().append("Converted procedure struct value parameter to member unwinding " + procedure.toString(getProgram()));
|
|
modified = true;
|
|
}
|
|
}
|
|
return modified;
|
|
}
|
|
|
|
/**
|
|
* Unwind an assignment to a struct value variable into assignment of each member
|
|
*
|
|
* @param assignment The assignment statement
|
|
* @param stmtIt The statement iterator used for adding/removing statements
|
|
* @param currentBlock The current code block
|
|
*/
|
|
public static boolean unwindAssignment(StatementAssignment assignment, ListIterator<Statement> stmtIt, Graph.Block currentBlock, Program program) {
|
|
LValue lValue = assignment.getlValue();
|
|
SymbolType lValueType = SymbolTypeInference.inferType(program.getScope(), lValue);
|
|
|
|
if(lValueType instanceof SymbolTypeStruct && assignment.getOperator() == null) {
|
|
|
|
// Assignment to a struct
|
|
SymbolTypeStruct lValueStructType = (SymbolTypeStruct) lValueType;
|
|
RValue rValue = assignment.getrValue2();
|
|
boolean initialAssignment = assignment.isInitialAssignment();
|
|
StatementSource source = assignment.getSource();
|
|
|
|
// Check if this is already a bulk assignment - or an unwound placeholder
|
|
if(rValue instanceof MemcpyValue || rValue instanceof MemsetValue || rValue instanceof StructUnwoundPlaceholder)
|
|
return false;
|
|
|
|
ValueSource lValueSource = ValueSourceFactory.getValueSource(lValue, program, program.getScope(), assignment, stmtIt, currentBlock);
|
|
ValueSource rValueSource = ValueSourceFactory.getValueSource(rValue, program, program.getScope(), assignment, stmtIt, currentBlock);
|
|
List<RValue> lValueUnwoundList = new ArrayList<>();
|
|
if(copyValues(lValueSource, rValueSource, lValueUnwoundList, initialAssignment, assignment, currentBlock, stmtIt, program)) {
|
|
if(lValue instanceof VariableRef) {
|
|
StructUnwoundPlaceholder unwoundPlaceholder = new StructUnwoundPlaceholder(lValueStructType, lValueUnwoundList);
|
|
assignment.setrValue2(unwoundPlaceholder);
|
|
} else {
|
|
stmtIt.remove();
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public static boolean copyValues(ValueSource lValueSource, ValueSource rValueSource, List<RValue> lValueUnwoundList, boolean initialAssignment, Statement currentStmt, Graph.Block currentBlock, ListIterator<Statement> stmtIt, Program program) {
|
|
if(lValueSource == null || rValueSource == null)
|
|
return false;
|
|
if(rValueSource instanceof ValueSourceParamValue && lValueSource.equals(((ValueSourceParamValue) rValueSource).getValueSource()))
|
|
return false;
|
|
if(lValueSource.equals(rValueSource))
|
|
return true;
|
|
if(lValueSource.isSimple() && rValueSource.isSimple()) {
|
|
stmtIt.previous();
|
|
LValue lValueRef = (LValue) lValueSource.getSimpleValue(program.getScope());
|
|
RValue rValueRef = rValueSource.getSimpleValue(program.getScope());
|
|
if(lValueUnwoundList != null)
|
|
lValueUnwoundList.add(lValueRef);
|
|
Statement copyStmt = new StatementAssignment(lValueRef, rValueRef, initialAssignment, currentStmt.getSource(), Comment.NO_COMMENTS);
|
|
stmtIt.add(copyStmt);
|
|
stmtIt.next();
|
|
if(program.getLog().isVerboseStructUnwind())
|
|
program.getLog().append("Adding value simple copy " + copyStmt.toString(program, false));
|
|
return true;
|
|
} else if(lValueSource.isBulkCopyable() && rValueSource.isBulkCopyable()) {
|
|
|
|
/* TODO: Attempt to mix struct mode classic with unwinding
|
|
if(lValueSource instanceof ValueSourceVariable && rValueSource instanceof ValueSourceVariable)
|
|
if(((ValueSourceVariable) lValueSource).getVariable().isStructClassic() && ((ValueSourceVariable) rValueSource).getVariable().isStructClassic())
|
|
return false;
|
|
*/
|
|
|
|
// Use bulk unwinding for a struct member that is an array
|
|
stmtIt.previous();
|
|
if(lValueSource.getArraySpec() != null)
|
|
if(rValueSource.getArraySpec() == null || !lValueSource.getArraySpec().equals(rValueSource.getArraySpec()))
|
|
throw new RuntimeException("ArraySpec mismatch!");
|
|
LValue lValueMemberVarRef = lValueSource.getBulkLValue(program.getScope());
|
|
RValue rValueBulkUnwinding = rValueSource.getBulkRValue(program.getScope());
|
|
if(lValueUnwoundList != null)
|
|
lValueUnwoundList.add(lValueMemberVarRef);
|
|
Statement copyStmt = new StatementAssignment(lValueMemberVarRef, rValueBulkUnwinding, initialAssignment, currentStmt.getSource(), Comment.NO_COMMENTS);
|
|
stmtIt.add(copyStmt);
|
|
stmtIt.next();
|
|
if(program.getLog().isVerboseStructUnwind())
|
|
program.getLog().append("Adding value bulk copy " + copyStmt.toString(program, false));
|
|
return true;
|
|
} else if(lValueSource.isUnwindable() && rValueSource.isUnwindable()) {
|
|
if(program.getLog().isVerboseStructUnwind())
|
|
program.getLog().append("Unwinding value copy " + currentStmt.toString(program, false));
|
|
|
|
//Special handling of unions
|
|
if(lValueSource.getSymbolType() instanceof SymbolTypeStruct) {
|
|
SymbolTypeStruct structType = (SymbolTypeStruct) lValueSource.getSymbolType();
|
|
if(structType.isUnion()) {
|
|
// Find the largest member to unwind to
|
|
final StructDefinition unionDefinition = structType.getStructDefinition(program.getScope());
|
|
int unionBytes = structType.getSizeBytes();
|
|
for(String memberName : lValueSource.getMemberNames(program.getScope())) {
|
|
final Variable unionMember = unionDefinition.getMember(memberName);
|
|
final int memberBytes = unionMember.getType().getSizeBytes();
|
|
if(memberBytes==unionBytes) {
|
|
// Found a union member with the total number of bytes - unwind to this member
|
|
ValueSource lValueSubSource = lValueSource.getMemberUnwinding(memberName, program, program.getScope(), currentStmt, stmtIt, currentBlock);
|
|
ValueSource rValueSubSource = rValueSource.getMemberUnwinding(memberName, program, program.getScope(), currentStmt, stmtIt, currentBlock);
|
|
boolean success = copyValues(lValueSubSource, rValueSubSource, lValueUnwoundList, initialAssignment, currentStmt, currentBlock, stmtIt, program);
|
|
if(!success)
|
|
throw new InternalError("Error during value unwinding copy! ", currentStmt);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Normal unwinding of non-unions
|
|
for(String memberName : lValueSource.getMemberNames(program.getScope())) {
|
|
ValueSource lValueSubSource = lValueSource.getMemberUnwinding(memberName, program, program.getScope(), currentStmt, stmtIt, currentBlock);
|
|
ValueSource rValueSubSource = rValueSource.getMemberUnwinding(memberName, program, program.getScope(), currentStmt, stmtIt, currentBlock);
|
|
boolean success = copyValues(lValueSubSource, rValueSubSource, lValueUnwoundList, initialAssignment, currentStmt, currentBlock, stmtIt, program);
|
|
if(!success)
|
|
throw new InternalError("Error during value unwinding copy! ", currentStmt);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
}
|