From 77db0e8701e6552756462784e73262ec6aa05462 Mon Sep 17 00:00:00 2001 From: jespergravgaard Date: Sun, 8 Aug 2021 13:47:48 +0200 Subject: [PATCH] Added support for calling a pointer to function without(*). Closes #692 --- .../Pass0GenerateStatementSequence.java | 140 +++++++++++------- .../kickc/passes/Pass1AssertJumpLabels.java | 2 + .../passes/Pass1GenerateControlFlowGraph.java | 2 + .../kickc/passes/Pass1Procedures.java | 28 ---- .../PassNAddTypeConversionAssignment.java | 23 ++- .../kickc/passes/PassNTypeInference.java | 3 + .../kickc/test/TestProgramsFast.java | 2 +- src/test/kc/function-pointer-return-2.c | 4 +- src/test/ref/function-pointer-return-2.asm | 6 +- src/test/ref/function-pointer-return-2.log | 10 +- 10 files changed, 133 insertions(+), 87 deletions(-) diff --git a/src/main/java/dk/camelot64/kickc/passes/Pass0GenerateStatementSequence.java b/src/main/java/dk/camelot64/kickc/passes/Pass0GenerateStatementSequence.java index f23bd370e..c24d1090d 100644 --- a/src/main/java/dk/camelot64/kickc/passes/Pass0GenerateStatementSequence.java +++ b/src/main/java/dk/camelot64/kickc/passes/Pass0GenerateStatementSequence.java @@ -120,10 +120,10 @@ public class Pass0GenerateStatementSequence extends KickCParserBaseVisitor statements = statementSequence.getStatements(); - if(statements.size()==0) + if(statements.size() == 0) return null; else - return statements.get(statements.size()-1); + return statements.get(statements.size() - 1); } @@ -198,13 +198,13 @@ public class Pass0GenerateStatementSequence extends KickCParserBaseVisitor(), new StatementSource(RuleContext.EMPTY), Comment.NO_COMMENTS)); final Procedure mainProc = program.getScope().getLocalProcedure(SymbolRef.MAIN_PROC_NAME); - if(mainProc==null) + if(mainProc == null) throw new CompileError("Required main() not defined in program."); if(!SymbolType.VOID.equals(mainProc.getReturnType()) && !SymbolType.SWORD.equals(mainProc.getReturnType())) throw new CompileError("return of main() must be 'void' or of type 'int'.", mainProc.getDefinitionSource()); - if(mainProc.getParameterNames().size()==0) { + if(mainProc.getParameterNames().size() == 0) { startSequence.addStatement(new StatementCall(null, SymbolRef.MAIN_PROC_NAME, new ArrayList<>(), new StatementSource(RuleContext.EMPTY), Comment.NO_COMMENTS)); - } else if(mainProc.getParameterNames().size()==2) { + } else if(mainProc.getParameterNames().size() == 2) { final List parameters = mainProc.getParameters(); final Variable argc = parameters.get(0); if(!SymbolType.SWORD.equals(argc.getType())) @@ -537,7 +537,7 @@ public class Pass0GenerateStatementSequence extends KickCParserBaseVisitor0) + if(typeName.length() > 0) typeName += " "; typeName += simpleTypeNode.getText(); } final SymbolType typeSimple = SymbolType.get(typeName); - if(typeSimple==null) - throw new CompileError("Unknown type '" +typeName+"'" , new StatementSource(ctx)); + if(typeSimple == null) + throw new CompileError("Unknown type '" + typeName + "'", new StatementSource(ctx)); varDecl.setDeclType(typeSimple); return null; } @@ -1929,7 +1929,7 @@ public class Pass0GenerateStatementSequence extends KickCParserBaseVisitor(); } - String procedureName; - RValue result; - if(ctx.expr() instanceof KickCParser.ExprIdContext) { - procedureName = ctx.expr().getText(); - // Handle the special BYTE0/1/2/3/WORD0/1/MAKEWORD/MAKEDWORD calls - if(Pass1ByteXIntrinsicRewrite.INTRINSIC_BYTE0_NAME.equals(procedureName) && parameters.size() == 1) { - result = addExprUnary(ctx, Operators.BYTE0, parameters.get(0)); - } else if(Pass1ByteXIntrinsicRewrite.INTRINSIC_BYTE1_NAME.equals(procedureName) && parameters.size() == 1) { - result = addExprUnary(ctx, Operators.BYTE1, parameters.get(0)); - } else if(Pass1ByteXIntrinsicRewrite.INTRINSIC_BYTE2_NAME.equals(procedureName) && parameters.size() == 1) { - result = addExprUnary(ctx, Operators.BYTE2, parameters.get(0)); - } else if(Pass1ByteXIntrinsicRewrite.INTRINSIC_BYTE3_NAME.equals(procedureName) && parameters.size() == 1) { - result = addExprUnary(ctx, Operators.BYTE3, parameters.get(0)); - } else if(Pass1ByteXIntrinsicRewrite.INTRINSIC_WORD0_NAME.equals(procedureName) && parameters.size() == 1) { - result = addExprUnary(ctx, Operators.WORD0, parameters.get(0)); - } else if(Pass1ByteXIntrinsicRewrite.INTRINSIC_WORD1_NAME.equals(procedureName) && parameters.size() == 1) { - result = addExprUnary(ctx, Operators.WORD1, parameters.get(0)); - } else if(Pass1ByteXIntrinsicRewrite.INTRINSIC_MAKEWORD.equals(procedureName) && parameters.size() == 2) { - result = addExprBinary(ctx, parameters.get(0), Operators.WORD, parameters.get(1)); - } else if(Pass1ByteXIntrinsicRewrite.INTRINSIC_MAKELONG.equals(procedureName) && parameters.size() == 2) { - result = addExprBinary(ctx, parameters.get(0), Operators.DWORD, parameters.get(1)); - } else { - // A normal named call - result = addIntermediateVar().getRef(); - addStatement(new StatementCall((LValue) result, procedureName, parameters, new StatementSource(ctx), ensureUnusedComments(getCommentsSymbol(ctx)))); - } - } else { - RValue procedurePointer = (RValue) this.visit(ctx.expr()); - result = addIntermediateVar().getRef(); - addStatement(new StatementCallPointer((LValue) result, procedurePointer, parameters, new StatementSource(ctx), ensureUnusedComments(getCommentsSymbol(ctx)))); - consumeExpr(procedurePointer); - Label afterCallLabel = getCurrentScope().addLabelIntermediate(); - addStatement(new StatementLabel(afterCallLabel.getRef(), new StatementSource(ctx), Comment.NO_COMMENTS)); - } for(RValue parameter : parameters) { consumeExpr(parameter); } - return result; + + if(ctx.expr() instanceof KickCParser.ExprIdContext) { + String procedureName = ctx.expr().getText(); + // Handle the special BYTE0/1/2/3/WORD0/1/MAKEWORD/MAKEDWORD calls + if(Pass1ByteXIntrinsicRewrite.INTRINSIC_BYTE0_NAME.equals(procedureName) && parameters.size() == 1) { + return addExprUnary(ctx, Operators.BYTE0, parameters.get(0)); + } else if(Pass1ByteXIntrinsicRewrite.INTRINSIC_BYTE1_NAME.equals(procedureName) && parameters.size() == 1) { + return addExprUnary(ctx, Operators.BYTE1, parameters.get(0)); + } else if(Pass1ByteXIntrinsicRewrite.INTRINSIC_BYTE2_NAME.equals(procedureName) && parameters.size() == 1) { + return addExprUnary(ctx, Operators.BYTE2, parameters.get(0)); + } else if(Pass1ByteXIntrinsicRewrite.INTRINSIC_BYTE3_NAME.equals(procedureName) && parameters.size() == 1) { + return addExprUnary(ctx, Operators.BYTE3, parameters.get(0)); + } else if(Pass1ByteXIntrinsicRewrite.INTRINSIC_WORD0_NAME.equals(procedureName) && parameters.size() == 1) { + return addExprUnary(ctx, Operators.WORD0, parameters.get(0)); + } else if(Pass1ByteXIntrinsicRewrite.INTRINSIC_WORD1_NAME.equals(procedureName) && parameters.size() == 1) { + return addExprUnary(ctx, Operators.WORD1, parameters.get(0)); + } else if(Pass1ByteXIntrinsicRewrite.INTRINSIC_MAKEWORD.equals(procedureName) && parameters.size() == 2) { + return addExprBinary(ctx, parameters.get(0), Operators.WORD, parameters.get(1)); + } else if(Pass1ByteXIntrinsicRewrite.INTRINSIC_MAKELONG.equals(procedureName) && parameters.size() == 2) { + return addExprBinary(ctx, parameters.get(0), Operators.DWORD, parameters.get(1)); + } else if(Pass1ByteXIntrinsicRewrite.INTRINSIC_MAKELONG4.equals(procedureName)) { + // Handle the intrinsic MAKELONG4() + if(program.getScope().getGlobalSymbol(Pass1ByteXIntrinsicRewrite.INTRINSIC_MAKELONG4) == null) { + // Add the intrinsic MAKEWORD4() to the global scope + final Procedure makeword4 = new Procedure( + Pass1ByteXIntrinsicRewrite.INTRINSIC_MAKELONG4, + new SymbolTypeProcedure(SymbolType.DWORD, Arrays.asList(new SymbolType[]{SymbolType.BYTE, SymbolType.BYTE, SymbolType.BYTE, SymbolType.BYTE})), + program.getScope(), + Scope.SEGMENT_CODE_DEFAULT, Scope.SEGMENT_DATA_DEFAULT, + Procedure.CallingConvention.INTRINSIC_CALL); + makeword4.setDeclaredIntrinsic(true); + final Variable hihi = new Variable("hihi", Variable.Kind.PHI_MASTER, SymbolType.BYTE, makeword4, Variable.MemoryArea.ZEROPAGE_MEMORY, Scope.SEGMENT_DATA_DEFAULT, null); + makeword4.add(hihi); + final Variable hilo = new Variable("hilo", Variable.Kind.PHI_MASTER, SymbolType.BYTE, makeword4, Variable.MemoryArea.ZEROPAGE_MEMORY, Scope.SEGMENT_DATA_DEFAULT, null); + makeword4.add(hilo); + final Variable lohi = new Variable("lohi", Variable.Kind.PHI_MASTER, SymbolType.BYTE, makeword4, Variable.MemoryArea.ZEROPAGE_MEMORY, Scope.SEGMENT_DATA_DEFAULT, null); + makeword4.add(lohi); + final Variable lolo = new Variable("lolo", Variable.Kind.PHI_MASTER, SymbolType.BYTE, makeword4, Variable.MemoryArea.ZEROPAGE_MEMORY, Scope.SEGMENT_DATA_DEFAULT, null); + makeword4.add(lolo); + makeword4.setParameters(Arrays.asList(hihi, hilo, lohi, lolo)); + program.getScope().add(makeword4); + } + // Add the call + LValue result = (LValue) addIntermediateVar().getRef(); + addStatement(new StatementCall(result, procedureName, parameters, new StatementSource(ctx), ensureUnusedComments(getCommentsSymbol(ctx)))); + return result; + } + } + + // Examine if the procedureName references a variable + RValue procedurePointer = (RValue) this.visit(ctx.expr()); + if(procedurePointer instanceof ProcedureRef) { + // A normal named call + LValue result = (LValue) addIntermediateVar().getRef(); + String procedureName = ((ProcedureRef) procedurePointer).getLocalName(); + addStatement(new StatementCall(result, procedureName, parameters, new StatementSource(ctx), ensureUnusedComments(getCommentsSymbol(ctx)))); + return result; + } else if(procedurePointer instanceof ForwardVariableRef) { + // TODO: Remove the need for forward references! + // Assume this is a named call to a yet undeclared function. + LValue result = (LValue) addIntermediateVar().getRef(); + String procedureName = ((ForwardVariableRef) procedurePointer).getName(); + addStatement(new StatementCall(result, procedureName, parameters, new StatementSource(ctx), ensureUnusedComments(getCommentsSymbol(ctx)))); + return result; + } else { + LValue result = (LValue) addIntermediateVar().getRef(); + addStatement(new StatementCallPointer(result, procedurePointer, parameters, new StatementSource(ctx), ensureUnusedComments(getCommentsSymbol(ctx)))); + consumeExpr(procedurePointer); + Label afterCallLabel = getCurrentScope().addLabelIntermediate(); + addStatement(new StatementLabel(afterCallLabel.getRef(), new StatementSource(ctx), Comment.NO_COMMENTS)); + return result; + } } @Override diff --git a/src/main/java/dk/camelot64/kickc/passes/Pass1AssertJumpLabels.java b/src/main/java/dk/camelot64/kickc/passes/Pass1AssertJumpLabels.java index a684211b8..3d4f36f9d 100644 --- a/src/main/java/dk/camelot64/kickc/passes/Pass1AssertJumpLabels.java +++ b/src/main/java/dk/camelot64/kickc/passes/Pass1AssertJumpLabels.java @@ -22,6 +22,8 @@ public class Pass1AssertJumpLabels extends Pass1Base { @Override public boolean step() { for(Procedure procedure : getProgram().getScope().getAllProcedures(true)) { + if(procedure.isDeclaredIntrinsic()) + continue; final ProcedureCompilation procedureCompilation = getProgram().getProcedureCompilation(procedure.getRef()); StatementSequence statementSequence = procedureCompilation.getStatementSequence(); for(Statement statement : statementSequence.getStatements()) { diff --git a/src/main/java/dk/camelot64/kickc/passes/Pass1GenerateControlFlowGraph.java b/src/main/java/dk/camelot64/kickc/passes/Pass1GenerateControlFlowGraph.java index 2090505d5..30b9a1190 100644 --- a/src/main/java/dk/camelot64/kickc/passes/Pass1GenerateControlFlowGraph.java +++ b/src/main/java/dk/camelot64/kickc/passes/Pass1GenerateControlFlowGraph.java @@ -29,6 +29,8 @@ public class Pass1GenerateControlFlowGraph extends Pass1Base { ProgramScope programScope = getScope(); final Collection allProcedures = getProgram().getScope().getAllProcedures(true); for(Procedure procedure : allProcedures) { + if(procedure.isDeclaredIntrinsic()) + continue; final ProcedureCompilation procedureCompilation = getProgram().getProcedureCompilation(procedure.getRef()); final StatementSequence sequence = procedureCompilation.getStatementSequence(); if(sequence.getStatements().size()==0) diff --git a/src/main/java/dk/camelot64/kickc/passes/Pass1Procedures.java b/src/main/java/dk/camelot64/kickc/passes/Pass1Procedures.java index 4bdd5dd52..63bd13316 100644 --- a/src/main/java/dk/camelot64/kickc/passes/Pass1Procedures.java +++ b/src/main/java/dk/camelot64/kickc/passes/Pass1Procedures.java @@ -8,11 +8,6 @@ import dk.camelot64.kickc.model.statements.StatementCall; import dk.camelot64.kickc.model.symbols.Procedure; import dk.camelot64.kickc.model.symbols.Scope; import dk.camelot64.kickc.model.symbols.Symbol; -import dk.camelot64.kickc.model.symbols.Variable; -import dk.camelot64.kickc.model.types.SymbolType; -import dk.camelot64.kickc.model.types.SymbolTypeProcedure; - -import java.util.Arrays; /** * Updates procedure calls to point to the actual procedure called. @@ -30,29 +25,6 @@ public class Pass1Procedures extends Pass2SsaOptimization { if(statement instanceof StatementCall) { StatementCall call = (StatementCall) statement; String procedureName = call.getProcedureName(); - if(procedureName.equals(Pass1ByteXIntrinsicRewrite.INTRINSIC_MAKELONG4)) { - if(getScope().getGlobalSymbol(Pass1ByteXIntrinsicRewrite.INTRINSIC_MAKELONG4) == null) { - // Add the intrinsic MAKEWORD4() - final Procedure makeword4 = new Procedure( - Pass1ByteXIntrinsicRewrite.INTRINSIC_MAKELONG4, - new SymbolTypeProcedure(SymbolType.DWORD, Arrays.asList(new SymbolType[]{ SymbolType.BYTE, SymbolType.BYTE, SymbolType.BYTE, SymbolType.BYTE})), - getScope(), - Scope.SEGMENT_CODE_DEFAULT, Scope.SEGMENT_DATA_DEFAULT, - Procedure.CallingConvention.INTRINSIC_CALL); - makeword4.setDeclaredIntrinsic(true); - final Variable hihi = new Variable("hihi", Variable.Kind.PHI_MASTER, SymbolType.BYTE, makeword4, Variable.MemoryArea.ZEROPAGE_MEMORY, Scope.SEGMENT_DATA_DEFAULT, null); - makeword4.add(hihi); - final Variable hilo = new Variable("hilo", Variable.Kind.PHI_MASTER, SymbolType.BYTE, makeword4, Variable.MemoryArea.ZEROPAGE_MEMORY, Scope.SEGMENT_DATA_DEFAULT, null); - makeword4.add(hilo); - final Variable lohi = new Variable("lohi", Variable.Kind.PHI_MASTER, SymbolType.BYTE, makeword4, Variable.MemoryArea.ZEROPAGE_MEMORY, Scope.SEGMENT_DATA_DEFAULT, null); - makeword4.add(lohi); - final Variable lolo = new Variable("lolo", Variable.Kind.PHI_MASTER, SymbolType.BYTE, makeword4, Variable.MemoryArea.ZEROPAGE_MEMORY, Scope.SEGMENT_DATA_DEFAULT, null); - makeword4.add(lolo); - makeword4.setParameters(Arrays.asList(hihi, hilo, lohi, lolo)); - getScope().add(makeword4); - } - } - Scope localScope = (Scope) getScope().getSymbol(block.getScope()); final Symbol procedureSymbol = localScope.findSymbol(procedureName); if(procedureSymbol == null) diff --git a/src/main/java/dk/camelot64/kickc/passes/PassNAddTypeConversionAssignment.java b/src/main/java/dk/camelot64/kickc/passes/PassNAddTypeConversionAssignment.java index c98996872..ca8385aa4 100644 --- a/src/main/java/dk/camelot64/kickc/passes/PassNAddTypeConversionAssignment.java +++ b/src/main/java/dk/camelot64/kickc/passes/PassNAddTypeConversionAssignment.java @@ -1,12 +1,16 @@ package dk.camelot64.kickc.passes; import dk.camelot64.kickc.model.CompileError; +import dk.camelot64.kickc.model.ControlFlowBlock; import dk.camelot64.kickc.model.Program; import dk.camelot64.kickc.model.iterator.ProgramExpressionBinary; import dk.camelot64.kickc.model.iterator.ProgramExpressionIterator; import dk.camelot64.kickc.model.iterator.ProgramValue; +import dk.camelot64.kickc.model.statements.Statement; +import dk.camelot64.kickc.model.statements.StatementCallPointer; import dk.camelot64.kickc.model.types.*; import dk.camelot64.kickc.model.values.ConstantSymbolPointer; +import dk.camelot64.kickc.model.values.PointerDereferenceSimple; import dk.camelot64.kickc.model.values.RValue; import dk.camelot64.kickc.model.values.SymbolRef; @@ -14,7 +18,6 @@ import java.util.concurrent.atomic.AtomicBoolean; /** * Add a cast a variable is assigned something of a convertible type. - * Also allows pointers to be assigned integer values. */ public class PassNAddTypeConversionAssignment extends Pass2SsaOptimization { @@ -65,6 +68,24 @@ public class PassNAddTypeConversionAssignment extends Pass2SsaOptimization { } } }); + + // Add dereference to call to pointer to function + for(ControlFlowBlock block : getProgram().getGraph().getAllBlocks()) { + for(Statement statement : block.getStatements()) { + if(statement instanceof StatementCallPointer) { + RValue procedure = ((StatementCallPointer) statement).getProcedure(); + SymbolType procType = SymbolTypeInference.inferType(getScope(), procedure); + if(procType instanceof SymbolTypePointer && ((SymbolTypePointer) procType).getElementType() instanceof SymbolTypeProcedure) { + // Allow calling pointer to procedure directly + // Add an automatic dereference to a pointer to procedure + if(!pass1 || getLog().isVerbosePass1CreateSsa()) + getLog().append("Adding dereference to call function pointer " + procedure.toString() + " in " + statement.toString(getProgram(), false)); + ((StatementCallPointer) statement).setProcedure(new PointerDereferenceSimple(procedure)); + } + } + } + } + return modified.get(); } diff --git a/src/main/java/dk/camelot64/kickc/passes/PassNTypeInference.java b/src/main/java/dk/camelot64/kickc/passes/PassNTypeInference.java index 4a355bcac..901471b8b 100644 --- a/src/main/java/dk/camelot64/kickc/passes/PassNTypeInference.java +++ b/src/main/java/dk/camelot64/kickc/passes/PassNTypeInference.java @@ -88,6 +88,9 @@ public class PassNTypeInference extends Pass2SsaOptimization { Variable symbol = programScope.getVariable((VariableRef) lValue); if(SymbolType.VAR.equals(symbol.getType()) || SymbolType.NUMBER.equals(symbol.getType())|| SymbolType.UNUMBER.equals(symbol.getType())|| SymbolType.SNUMBER.equals(symbol.getType())) { SymbolType procedureType = SymbolTypeInference.inferType(programScope, call.getProcedure()); + if(procedureType instanceof SymbolTypePointer) + // Handle call to pointer to function + procedureType = ((SymbolTypePointer) procedureType).getElementType(); if(procedureType instanceof SymbolTypeProcedure) { SymbolType returnType = ((SymbolTypeProcedure) procedureType).getReturnType(); setInferedType(program, call, symbol, returnType); diff --git a/src/test/java/dk/camelot64/kickc/test/TestProgramsFast.java b/src/test/java/dk/camelot64/kickc/test/TestProgramsFast.java index 211748e14..d68f0fb50 100644 --- a/src/test/java/dk/camelot64/kickc/test/TestProgramsFast.java +++ b/src/test/java/dk/camelot64/kickc/test/TestProgramsFast.java @@ -271,7 +271,7 @@ public class TestProgramsFast extends TestPrograms { @Test public void testLocalVarShadowingProcedure() throws IOException { - assertError("local-var-shadowing-procedure.c", "Called symbol is not a procedure. main::doit"); + assertError("local-var-shadowing-procedure.c", "Called object is not a function or function pointer main::doit"); } @Test diff --git a/src/test/kc/function-pointer-return-2.c b/src/test/kc/function-pointer-return-2.c index 31e81e2aa..ff9e92927 100644 --- a/src/test/kc/function-pointer-return-2.c +++ b/src/test/kc/function-pointer-return-2.c @@ -13,11 +13,13 @@ char fn2() { } void set_border(char (*fn)(void)) { - *BORDER = (*fn)(); + // Call pointer to a function without * + *BORDER = fn(); } void main() { for(;;) { + // Create pointer to function without & set_border(fn1); set_border(fn2); } diff --git a/src/test/ref/function-pointer-return-2.asm b/src/test/ref/function-pointer-return-2.asm index 2c9865984..9839be346 100644 --- a/src/test/ref/function-pointer-return-2.asm +++ b/src/test/ref/function-pointer-return-2.asm @@ -15,6 +15,7 @@ main: { __b1: // set_border(fn1) + // Create pointer to function without & lda #fn1 @@ -49,11 +50,12 @@ fn1: { // set_border(byte()* zp(2) fn) set_border: { .label fn = 2 - // (*fn)() + // fn() pha jsr bi_fn pla - // *BORDER = (*fn)() + // *BORDER = fn() + // Call pointer to a function without * sta BORDER // } rts diff --git a/src/test/ref/function-pointer-return-2.log b/src/test/ref/function-pointer-return-2.log index c0c3408d5..e35dcb7f9 100644 --- a/src/test/ref/function-pointer-return-2.log +++ b/src/test/ref/function-pointer-return-2.log @@ -263,6 +263,7 @@ main: { // main::@1 __b1: // [2] call set_border + // Create pointer to function without & // [11] phi from main::@1 to set_border [phi:main::@1->set_border] set_border_from___b1: // [11] phi set_border::fn#2 = &fn1 [phi:main::@1->set_border#0] -- pprz1=pprc1 @@ -329,6 +330,7 @@ set_border: { // set_border::@1 __b1: // [15] *BORDER = set_border::$0 -- _deref_pbuc1=vbuaa + // Call pointer to a function without * sta BORDER jmp __breturn // set_border::@return @@ -351,10 +353,10 @@ Succesful ASM optimization Pass5NextJumpElimination Replacing label __b1_from___b2 with __b1 Removing instruction __b1_from_main: Removing instruction __b1_from___b2: -Removing instruction set_border_from___b1: Removing instruction __b2_from___b1: Removing instruction set_border_from___b2: Succesful ASM optimization Pass5RedundantLabelElimination +Removing instruction set_border_from___b1: Removing instruction __b2: Removing instruction __breturn: Removing instruction __breturn: @@ -412,6 +414,7 @@ main: { __b1: // set_border(fn1) // [2] call set_border + // Create pointer to function without & // [11] phi from main::@1 to set_border [phi:main::@1->set_border] // [11] phi set_border::fn#2 = &fn1 [phi:main::@1->set_border#0] -- pprz1=pprc1 lda #