summaryrefslogtreecommitdiff
path: root/chapter-escape/src/test/java/com/seaofnodes/simple/EscapeTest.java
blob: 6d7783503a8ec9cd3ee67ca6b216c149b6babd3a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package com.seaofnodes.simple;

import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.*;
import org.junit.Test;
import static org.junit.Assert.*;

public class EscapeTest {
    private static final String POWER = """
        val power = { int base, int exponent ->
            if (exponent <= 0) return 1;
            int smaller=power(base,exponent-1);
            return base * smaller;
        };
        """;
    private static final String SUM = """
        val sumPowers = { int n, {int int -> int} f ->
            if (n <= 0) return 0;
            return f(2,n) + sumPowers(n-1,f);
        };
        """;
    private static ParmNode base(CodeGen code) {
        ParmNode parm=code._start.walk(n -> n instanceof ParmNode p && p._label.equals("base") ? p : null);
        assertNotNull(parm);
        return parm;
    }
    private static CodeGen analyze(String src, long seed) {
        return new CodeGen(src,TypeInteger.BOT,seed,true).driver(CodeGen.Phase.Opto);
    }

    @Test public void testKnownRecursiveCallback() {
        String src=POWER+SUM+"return sumPowers(arg,power);";
        for( long seed : new long[]{2,13,123} ) {
            CodeGen code=analyze(src,seed);
            ParmNode base=base(code);
            assertSame(TypeInteger.constant(2),base._type);
            assertFalse(base.fun().unknownCallers());
            assertNotNull(code._start.walk(n -> n instanceof CallNode c && "power".equals(c.name()) ? c : null));
            code.driver(CodeGen.Phase.TypeCheck);
            assertEquals("14",Eval2.eval(code,3));
        }
    }

    @Test public void testReturnedAddresses() {
        // Returning the address, or a chain of objects containing it, restores
        // arbitrary callers even when every visible call passes base=2.
        String holder="struct Holder { {int int -> int} f; }; val h=new Holder { f=power; };";
        for( String tail : new String[]{
            "return power;",
            holder+"return h;",
            holder+"struct Wrap { Holder child; }; return new Wrap { child=h; };",
            "struct Cell { {int int -> int}? !f; }; val h=new Cell; while(arg--) h.f=power; return h;"
        } ) {
            CodeGen code=analyze(POWER+SUM+"sumPowers(arg,power);"+tail,123);
            ParmNode base=base(code);
            assertSame(TypeInteger.BOT,base._type);
            assertTrue(base.fun().unknownCallers());
            assertTrue(code._start.escapedFIDX(base.fun().sig().fidx()));
            code.driver(CodeGen.Phase.TypeCheck);
        }
    }

    @Test public void testNativeExposure() {
        for( String tail : new String[]{
            "retain(power); return sumPowers(arg,power);",
            "if(arg) { retain(power); while(true) {} } return sumPowers(arg,power);"
        } ) {
            String src=POWER+SUM+"{ {int int -> int} -> int } retain=\"C\";"+tail;
            CodeGen code=analyze(src,123);
            ParmNode base=base(code);
            assertSame(TypeInteger.BOT,base._type);
            assertTrue(base.fun().unknownCallers());
            code.driver(CodeGen.Phase.TypeCheck);
        }
        // An external caller can inspect a field written through its argument,
        // even though the exposed function itself returns only an integer.
        String src="struct Holder { {int int -> int}? !f; };"+POWER+
            "val expose={!Holder h -> h.f=power; return 0;}; return expose;";
        CodeGen code=analyze(src,123);
        assertTrue(base(code).fun().unknownCallers());
        code.driver(CodeGen.Phase.TypeCheck);
    }
}