summaryrefslogtreecommitdiff
path: root/src/main/java/com/seaofnodes/simple/parser/Structs.java
blob: db17cea32e0bc24bb5b39ebe94b77394d247a09b (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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
package com.seaofnodes.simple.parser;

import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.node.ScopeNode.Kind;
import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.Ary;
import java.util.IdentityHashMap;

/** Struct and constructor declarations, field discovery, and initialization checks. */
final class Structs {
    private final Parser P;

    // Constructor exit types survive scope teardown until all fields are
    // known.  A null entry denotes a constructor with no returning path.
    Ary<IdentityHashMap<Var,Type>> _ctorExits;

    // Constructor declaration: new TypeName = { args -> body };
    record Constructor(String name, Lexer body) {}
    Constructor _pendingCtor;

    Structs(Parser P) { this.P = P; }

    /**
     * Parse a struct declaration, and return the following statement.
     * Structs cannot be redefined, but can be nested.
     *
     * @return zero
     */
    Node parseStruct( ) {
        // keyword "struct" already parsed, so expect the struct name next.
        String typeName = P.requireId();
        P.require("{");

        // create type class:NESTED.typeName; no fields.

        String old = P._nestedType;
        String fullName = P._scope.depth()==1 && P._nestedType.endsWith(typeName) ? old : (old+"."+typeName).intern();
        P._nestedType = fullName; // Recursive structs start with this as their basename

        ReturnNode ret = parseInitializer( false, fullName );

        P._nestedType = old;

        // Insert a field into class:NESTED.typeName of "val typeName = fcn-ptr-to-init"
        // Function does malloc internally.
        // Future self: means subtyping has to deal with "who does the malloc"
        String clzName = Parser.addClzPrefix(fullName);
        TypeFunPtr sig = ret.fun().sig();
        TypeStruct ts = (TypeStruct)Parser.TYPES.get(clzName);
        String initName = initFieldName(fullName);
        Field oldFld = ts==null ? null : ts.field(initName);
        Field fld = oldFld==null
            ? Field.make(initName,sig,P._code.alias(P._ref._cname),true)
            : oldFld.makeFrom(sig);
        Field ctorFld = ts==null ? null : ts.field("<ctor>");
        // A same-named struct shares its file's class object and flag.
        Field flag = ts==null ? null : ts.field("$init");
        ts = ctorFld==null
            ? TypeStruct.make(clzName,false,fld)
            : TypeStruct.make(clzName,false,fld,ctorFld);
        if( flag!=null ) ts = ts.add(flag);
        TypeFunPtr alloc = P.allocations.makeAllocator((TypeStruct)Parser.TYPES.get(fullName),ret,ctorFld);
        ts = ts.add(Field.make("<new>",alloc,P._code.alias(P._ref._cname),true));
        Parser.TYPES.put(clzName,ts);

        // Insert a field in the containing class with the nested class type.
        // This is basically sugar for "val CLZNAME = <init>"
        // and is a nicer version of updateSelfAsFieldsDiscovered.
        P._scope.define(typeName, sig, true, new FunPtrNode(sig,P._code._start,ret).peephole(), P.loc());

        P.require("}");
        return P.require(P._code.ZERO,";");
    }

    boolean inStructDeclaration() {
        return P._scope.klast() instanceof Kind.Func fun && FunNode.isInstance(fun._name);
    }

    Node parseConstructorDeclaration() {
        TypeStruct self = P._scope.constructorSelf();
        assert self != null;
        String name = P.requireId();
        String expect = initFieldName(self._name);
        if( name != expect )
            throw P.error("Constructor name '" + name + "' must match type name '" + expect + "'");
        P.require("=");
        if( _pendingCtor!=null ) throw P.error("Redefining constructor '"+name+"'");
        _pendingCtor = new Constructor(name,P.loc());
        // Parse the body after all field declarations are known, so self.x
        // and x share a local slot even when x is declared below this body.
        P.require("{");
        P._lexer.skipTo('{','}',"constructor body");
        P.require("}");
        P.require(";");
        return P._code.ZERO;
    }

    void finishConstructor() {
        if( _pendingCtor==null ) return;
        Constructor ctor = _pendingCtor;
        _pendingCtor = null;
        Lexer after = P._lexer;
        P._lexer = new Lexer(ctor.body());
        defineConstructor(ctor.name());
        P._lexer = after;
    }

    private void defineConstructor(String name) {
        TypeStruct self = P._scope.constructorSelf();
        Node ctor = P.functions.parseConstructor(name);
        TypeFunPtr sig = (TypeFunPtr)ctor._type;
        String clzName = Parser.addClzPrefix(self._name);
        TypeStruct clz = (TypeStruct)Parser.TYPES.get(clzName);
        Field old = clz==null ? null : clz.field("<ctor>");
        if( old != null )
            throw P.error("Redefining constructor '" + name + "'");
        Field fld = Field.make("<ctor>",sig,P._code.alias(P._ref._cname),true);
        Parser.TYPES.put(clzName,clz==null ? TypeStruct.make(clzName,true,fld) : clz.add(fld));
        P.require(ctor,";");
    }

    // Parse a struct declaration (not an allocation); file-level is a
    // class-init, and scope structs are normal
    ReturnNode parseInitializer( boolean isClz, String typeName ) {
        var oldExits = _ctorExits;
        _ctorExits = new Ary(IdentityHashMap.class);
        // Record & restore global state set during parsing the <init> code
        Node oldCtrl= P._scope.ctrl().keep();
        Node oldMem = P._scope.mem ().keep();

        // Make the future clazz/instance struct.
        TypeStruct tself = (TypeStruct)Parser.TYPES.get(typeName);
        assert tself==null || tself._open;
        // Close an existing open struct, or define a forward ref
        tself = tself==null ? TypeStruct.make(typeName,true) : tself.defined();

        // Class object keeps a runtime flag `$init` to signal the <clinit> has run.
        if( isClz )
            tself = tself.add(Field.make("$init",TypeInteger.BOOL,P._code.alias(P._ref._cname),false));
        Parser.TYPES.put(typeName, tself);

        // Signature of the default constructor
        TypeFunPtr sig = structInitSig(isClz,tself);
        // Function and argument names of default constructor
        String fname = (typeName + (isClz ? ".<clinit>" : ".<init>")).intern();
        String[] ids = new String[]{"self", isClz ? "arg" : "#selfMem"};

        // Struct decls look like function bodies.  Parse function body normally.
        ReturnNode ret = P.functions.parseBody(fname,sig,P.loc(),ids);
        _ctorExits = oldExits;
        // Unwind global state.
        P.ctrl(oldCtrl.unkeep());
        P.mem (oldMem .unkeep());
        return ret;
    }

    // <cl/init> signature: { self arg/selfMem -> BOT/selfMem }
    // Self-memory is the rare *private* (never aliased) memory for the new object.
    private TypeFunPtr structInitSig( boolean isClz, TypeStruct tself ) {
        // Private uninitialized memory from new object, no escaped values.
        TypeMem privMem = isClz ? null : TypeMem.make(1,tself,true,isClz,false,null,null);
        Type targ = isClz ? TypeInteger.BOT : privMem;
        // Return for private memory has to name all escaped fields, which are not known yet.
        // Escape ALL fields, and sharpen later.
        Type tret = isClz ? Type.BOTTOM     : TypeMem.makePrivate(tself);
        Type[] targs = new Type[]{ TypeMemPtr.make((byte)2,tself,isClz), targ };
        // FIDX for FREFs is in the global namespace, needs the correct clz name.
        String clzBare = tself._name;
        int order = 3; // 3 is always the <init> order, 2nd in the file just behind the <clinit>
        if( isClz ) {
            assert Parser.startsClzPrefix( clzBare );
            clzBare = clzBare.substring(Parser.CLZ.length()).intern();
            order = 2; // 2 is always the <clinit> order, first in file and skipping reserved #1
        }
        return TypeFunPtr.make1((byte)2, true, targs, tret, P._code.fidx(clzBare,order));
    }

    int updateSelfAsFieldsDiscovered( String typeName, int nvar ) {
        int lex = P._scope.klast()._lexSize + 2/*Skip self and selfMem*/;
        // Load self type in and out of the TYPES hashtable.
        TypeStruct tself = (TypeStruct)Parser.TYPES.get(typeName);
        // Start looking for newly declared vars at the lexical scope start,
        // skipping two ("self" and "selfmem") and fields seen already.
        while( lex + nvar < P._scope.nIns() ) {
            Var v = P._scope.var(lex + nvar++);
            // Field with sharper type
            Field old = tself.field(v._name);
            Field fld = v._extern!=0
                ? Field.make(v._name,v.type(),v._extern,false,true)
                : old == null
                ? Field.make(v._name,v.type(),P._code.alias(P._ref._cname),v._final)
                : old.makeFrom(v.type());
            tself = tself.addOrUpdate(fld);
        }
        Parser.TYPES.put(typeName,tself);
        return nvar;
    }

    void upgradeSelfTypeAndStoreFields( String typeName, boolean isClz, ParmNode self, ParmNode smem, boolean upgrade ) {
        int lex = P._scope.klast()._lexSize;
        assert P._scope.var(lex)._name=="self";
        TypeStruct tself = (TypeStruct)Parser.TYPES.get(typeName);
        int base = lex+2;       // Skip ctrl, mem
        if( upgrade ) {
            // Default <init>/<clinit> defines the struct and can sharpen fields.
            // A named constructor only initializes the already-closed struct.
            for( int i = 0; i<tself.nkids(); i++ ) {
                Field tfld = tself._fields[i];
                int vidx = fieldVarIdx(base,tfld._fname);
                if( vidx == -1 )
                    continue;
                Var v = P._scope.var(vidx);
                // Forward-ref fields where placed in tself but will get promoted
                // to an outer scope - and should not be in tself.
                if( v._fref )
                    tself = tself.remove(i--);
                else if( v.type() != tfld._t ) {
                    assert v.type().isa(tfld._t);
                    tself = tself.replace( tfld.makeFrom( v.type() ) );
                }
            }

            // Close Struct type after parsing struct body.
            Parser.TYPES.put( tself._name, tself = tself.close() );
            // Improve self, selfMem types in scope.
            // Class self-type is a singleton pointer.  Instance <init> receives the
            // broad allocation pointer, while private memory tracks initialized fields.
            self._declaredType = self._type = isClz ? TypeMemPtr.make((byte)2,tself,true) : TypeMemPtr.make(constructorRecv(tself));
            FunNode fun = self.fun();
            fun.setSig(fun.sig().makeFrom(self._type,0));
            if( !isClz )        // <init> returns upgraded private memory
                smem._declaredType = smem._type = TypeMem.makePrivate(tself);
        }

        if( !upgrade ) {
            IdentityHashMap<Var,Type> exit = null;
            if( P.functions._returnScope!=null && !P.functions._returnScope.ctrl()._type.isHigh() ) {
                exit = new IdentityHashMap<>();
                int first = P._scope._kinds.at(P._scope.depth()-2)._lexSize+2;
                for( int i = first; i<lex; i++ )
                    exit.put(P._scope.var(i),P.functions._returnScope.in(i)._type);
            }
            _ctorExits.add(exit);
        } else if( !isClz ) {
            // Check that all fields are initialized
            checkInitFields(tself,base);
        }

        // When can _returnScope be null here? A never-exit constructor will
        // not have any returns, and thus no need to gather values and store
        // them into the (never) constructed object
        if( P.functions._returnScope==null )
            return;

        // A MemMerge to gather field updates as stores.  Classes update the
        // normal public memory.  <init> updates the private memory that got
        // passed in - is treated like a normal argument and not like memory.
        Node mmm = isClz ? P.functions._returnScope.mem(1) : smem;

        // Store constructor results into fields
        for( int i = 0; i<tself.nkids(); i++ ) {
            Field fld = tself._fields[i];
            int vidx = upgrade
                ? fieldVarIdx(base,fld._fname)
                : fieldVarIdxBefore(lex,fld._fname);
            if( vidx == -1 )
                continue;
            Var v = P._scope.var(vidx);
            if( v._fref || v._extern!=0 ) continue; // C storage has no Simple initializer
            // NewNode's TOP field is the zero-code poison for a required
            // field. Leave it alone unless the declaration body supplied a
            // value; otherwise a user constructor must initialize it.
            Node val = P.functions._returnScope.in(vidx);
            if( upgrade && v._uninit &&
                !initialized(val._type) ) continue;
            // A numeric merge can still be untyped while parsing. Retain its
            // declared conversion at the helper boundary, as assignments do.
            if( fld._t instanceof TypeInteger || fld._t instanceof TypeFloat )
                val = P.conversions.convertExpr(val,fld._t);
            // Store value into extended struct
            Node prior = mmm.keep();
            Node st = P.peep(new StoreNode(null, fld._fname, fld._alias, fld._t, null, prior, self, Parser.off(tself,fld._fname), val, fld._final));
            mmm = P.mergeAlias(prior,fld._alias,st);
            prior.unkeep();
        }
        mmm = mmm.peephole();

        if( !isClz )
            // Stuff private mem into normal expression return value.
            P.functions._returnScope.setDef(P.functions._returnScope.nIns()-1,mmm);
        else
            // Parser memory is one opaque whole-memory edge.  Publish the
            // completed class-field aggregate as the <clinit> return memory.
            P.functions._returnScope.mem(mmm);
            //_code.add(mmm);
    }

    // Check that all fields are initialized on all constructor exit paths.
    private void checkInitFields(TypeStruct ts, int base) {
        if( P.functions._returnScope==null || P.functions._returnScope.ctrl()._type.isHigh() ) return;
        boolean implicit = _ctorExits.isEmpty();
        if( implicit ) _ctorExits.add(new IdentityHashMap<>());
        for( IdentityHashMap<Var,Type> exit : _ctorExits ) {
            if( exit==null ) continue;
            for( Field fld : ts._fields ) {
                int idx = fieldVarIdx(base,fld._fname);
                if( idx == -1 ) continue;
                Var v = P._scope.var(idx);
                if( !v._uninit || v._fref ) continue;
                Type t = exit.getOrDefault(v,P.functions._returnScope.in(idx)._type);
                if( !initialized(t) )
                    throw Parser.error("'"+ts._name+"' is not fully initialized, field '"+fld._fname+
                                (implicit ? "' needs to be set in a constructor" :
                                            "' is only partially set in the constructor"),v._loc);
            }
        }
    }

    // Seed the constructor-local SSA slots from the declaration initializer.
    void constructorDefaults(String typeName, FunNode fun, Lexer loc) {
        int lex = P._scope.klast()._lexSize;
        TypeStruct shape = (TypeStruct)Parser.TYPES.get(typeName);
        for( Field fld : shape._fields ) {
            int idx = fieldVarIdxBefore(lex,fld._fname);
            if( idx<0 ) continue;
            Var v = P._scope.var(idx);
            if( v._fref || v._extern!=0 || v._uninit || P._scope.in(v)._type.isConstant() ) continue;
            Node value = P.conversions.convertExpr(
                new LoadNode(loc,fld._fname,fld._alias,P.ctrl(),fun.parm(3),fun.parm(2),Parser.off(shape,fld._fname)).peephole(),v.type());
            P._scope.update(v,value);
        }
    }

    void beginClassInit(FunNode fun, Lexer loc) {
        // Each initializer establishes its parent before setting its own
        // run-once flag. Keep this in the body so every execution strategy
        // shares the same ordering, including direct entry into a child.
        if( P._ref._par!=null ) {
            String pname = Parser.addClzPrefix(P._ref._par._cname);
            TypeStruct parent = (TypeStruct)Parser.TYPES.get(pname);
            if( parent==null ) parent = TypeStruct.make(pname,true);
            Node ptr = P.con(TypeMemPtr.make((byte)2,parent,true)).keep();
            P.classInit(loc,ptr);
            ptr.unkeep();
        }
        Node self = fun.parm(2);
        Field flag = ((TypeMemPtr)self._type)._obj.field("$init");
        Node prior = P.mem().keep();
        Node st = P.peep(new StoreNode(loc,"$init",flag._alias,flag._t,P.ctrl(),prior,self,
                                    P.fldoff(self,"$init"),P.con(1),false));
        P.storeMem(st,flag._alias,prior);
        prior.unkeep();
    }

    // Validate each user constructor, or an implicit empty one, before the
    // declaration scope goes away. Later fields use their declaration defaults.
    static boolean initialized(Type t) {
        // Can't be BOTTOM, that means explicit not initialized.
        // Can't be high (initialized to an error)
        // Can't be left null and declared not-null.
        return t!=Type.BOTTOM && !t.isHigh() && (!(t instanceof TypeNil tn) || tn.notNull());
    }

    private int fieldVarIdx( int base, String name ) {
        for( int i = base; i<P._scope.nIns(); i++ )
            if( P._scope.var(i)._name == name )
                return i;
        return -1;
    }

    // Named constructors update field Vars from the enclosing initializer
    // scope.  Constructor arguments and locals begin at limit and must not
    // shadow the field state gathered into private memory.
    int fieldVarIdxBefore( int limit, String name ) {
        for( int i = limit-1; i>=0; i-- )
            if( P._scope.var(i)._name == name )
                return i;
        return -1;
    }

    boolean isConstructorDecl(String name) {
        TypeStruct self = P._scope.constructorSelf();
        return self != null && name == initFieldName(self._name);
    }

    boolean inExplicitConstructor() {
        Kind.Func func = (Kind.Func)P._scope._kinds.at(P._scope.enclosingFuncOrDecl());
        return isExplicitConstructorName(func._name);
    }

    boolean isExplicitConstructorName(String funName) {
        TypeStruct self = P._scope.constructorSelf();
        return self != null && funName == (self._name+"."+initFieldName(self._name)).intern();
    }

    static TypeStruct constructorRecv(TypeStruct self) {
        Field[] fs = new Field[self._fields.length];
        for( int i = 0; i<fs.length; i++ ) {
            Field fld = self._fields[i];
            fs[i] = fld.makeFrom(fld._t.makeStorage());
        }
        return TypeStruct.make(self._name,self._open,fs);
    }

    private static String initFieldName( String typeName ) {
        if( Parser.startsClzPrefix(typeName) )
            typeName = typeName.substring(Parser.CLZ.length());
        int idx = typeName.lastIndexOf('.');
        return (idx == -1 ? typeName : typeName.substring(idx+1)).intern();
    }
}