src/Zone.h
author David Anderson <dvander@alliedmods.net>
Sat Nov 17 18:41:01 2012 -0800 (2012-11-17)
changeset 195 c1ba166f3fc4
parent 188 bab29bcec7fe
child 201 54f3a4f41f61
permissions -rw-r--r--
Massive SemA/BC refactoring to support better type systems. Read on for more.

The previous compiler had a simple pipeline, best outlined as:
(1) Parsing and name binding -> AST
(2) Storage allocation -> AST
(3) Semantic Analysis (SemA) -> AST
(4) Bytecode Compilation (BC) -> Code

This pipeline, unfortunately, had two problems. First, name binding cannot be performed in one pass if we wish to have multi-file support in the form of modules. Name binding must be two-pass.

Second, because SemA was only capable of annotating AST nodes with a single type, most coercion work had to be duplicated in the BC phase, as coercion was not explicit in the SemA output. This made introducing new type rules extremely difficult, and all but precluded concepts like operator overloading (or overloading at all).

This patch rewrites most of Keima's backend. Of interest are the following changes:
(1) CompileContext has been refactored around future multi-file support.
(2) Parsing no longer performs any name binding.
(3) The grammar for type-and-name has been changed from:
(Label | Identifier)? Identifier
to:
Type? Identifier
where:
Type ::= OldType | NewType
OldType ::= Label
NewType ::= Identifier (. NewType)*


Although we do not implement the full production for Type yet, this
distinction is important. A type identifier is now affixed to the AST
as an Expression (right now, always a NameProxy), so it can fully
participate in name binding.

(4) Immediately after parsing, the AST goes through a NamePopulation phase.
This phase creates Scope objects for every scope which declares a name,
and if those names are declaring types or functions, a Symbol is created
and entered into the scope. Symbols entirely replace the old BoundName
classes.

(5) After NamePopulation comes NameBinding. This phase performs three steps:
(a) Links Scope objects together, to form a tree.
(b) Binds any free name to an existing name in the scope chain.
(c) Creates and registers any Symbols for names which have not yet been
added to the scope (for example, local variables).

In accordance, all "allocation" and "binding" concepts have been removed
from Scopes, which are now lightweight container classes.

(6) SemA now generates bytecode for each statement in the AST. Expression
compilation is performed via a separate mechanism called HIR (High-level
Intermediate Representation). To analyze an expression, SemA will walk
the AST, and produce a HIR object for each node. HIR is typed, and SemA
may insert coercion nodes, or even expand an AST node into multiple HIR
nodes. The result of evaluating an expression in SemA is therefore the
the root of a HIR tree, which SemA then sends to the HIRTranslator, which
performs bytecode generation.

As before, SemA still performs full semantic analysis. However, not it
also produces each function's bytecode.

This split allows us to decompose potentially complex semantics into
a more fine-grained, AST-like structure, which can have very simple
code-generation logic. For example, (1.0 + 5) might look like:

HAdd(HFloat(1.0), HConvert(HInteger(5), <Float>))

As part of this decomposition, many opcodes have been removed. Rather than
using typed jumps, we now expand jumps into longer tests. For example:
jge.f <label>
becomes:
ge.f
cvt.f2b
jt <label>

This simplifies the pipeline, and JITs should be able to melt the added
work away.

HIR is not used at a statement level, and HIR does not have any concept
of control-flow.

(7) The old BytecodeCompiler has been removed, as its work is now split
between SemA and HIRTranslator.


In addition, some minor refactorings have taken place:

(1) Opcodes.tbl no longer hardcodes numbers (aaah).
(2) Type has been split into smaller, typed structs.
(3) BytecodeEmitter no longer relies on Pools or RootScopes.
(4) SemA is now responsible for variable/storage allocation.
(5) Native table are now global, rather than per-module. As such, native
declarations now result in a Native object (which still requires a
CALLNATIVE opcode), which is an index into the global table. Natives
must be bound globally. This is in preparation for module support.
(6) Publics are no longer tracked, but rather registered via a global
callback upon module load. This callback can be set by embedders. This
is in preparation for multi-file support.
(7) The BytecodeEmitter now performs Symbol allocations itself, and it
also generates Code objects itself, which removes a good deal of
complexity.

Finally, a test harness has been added. This harness is a python script which finds *.test files, recursively, in the tests folder. For each file it runs the corresponding .sp. The contents of the .test file must match stdout+stderr.
dvander@28
     1
/* vim: set ts=4 sw=4 tw=99 et:
dvander@28
     2
 *
dvander@0
     3
 * Copyright (C) 2012 David Anderson
dvander@0
     4
 *
dvander@0
     5
 * This file is part of JITCraft.
dvander@0
     6
 *
dvander@0
     7
 * JITCraft is free software: you can redistribute it and/or modify it under
dvander@0
     8
 * the terms of the GNU General Public License as published by the Free
dvander@0
     9
 * Software Foundation, either version 3 of the License, or (at your option)
dvander@0
    10
 * any later version.
dvander@0
    11
 * 
dvander@0
    12
 * Foobar is distributed in the hope that it will be useful, but WITHOUT ANY
dvander@0
    13
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
dvander@0
    14
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
dvander@0
    15
 *
dvander@0
    16
 * You should have received a copy of the GNU General Public License along with
dvander@0
    17
 * JITCraft. If not, see http://www.gnu.org/licenses/.
dvander@0
    18
 */
dvander@0
    19
#ifndef _include_jitcraft_thread_h_
dvander@0
    20
#define _include_jitcraft_thread_h_
dvander@0
    21
dvander@69
    22
#include <stdarg.h>
dvander@0
    23
#include "PoolAllocator.h"
dvander@0
    24
#include "Stack.h"
dvander@11
    25
#include "Heap.h"
dvander@11
    26
#include "Barriers.h"
dvander@27
    27
#include "Handles.h"
dvander@69
    28
#include "Messages.h"
dvander@29
    29
#include "RootScopes.h"
dvander@45
    30
#include "VirtualObjectVisitor.h"
dvander@0
    31
dvander@59
    32
namespace ke {
dvander@0
    33
dvander@11
    34
class TypeManager;
dvander@54
    35
class String;
dvander@54
    36
class StringPool;
dvander@61
    37
class GlobalRoots;
dvander@65
    38
class ArrayMap;
dvander@69
    39
class Exception;
dvander@195
    40
class NativeTable;
dvander@11
    41
dvander@0
    42
class Zone
dvander@0
    43
{
dvander@0
    44
    PoolAllocator pool_;
dvander@0
    45
    Stack stack_;
dvander@11
    46
    Heap heap_;
dvander@11
    47
dvander@11
    48
    // Map cache.
dvander@11
    49
    Unbarriered<Map> maps_[MapKinds_Total];
dvander@188
    50
    Unbarriered<ArrayMap> externalGenericArrayMap_;
dvander@188
    51
    Unbarriered<ArrayMap> externalCharArrayMap_;
dvander@11
    52
dvander@11
    53
    TypeManager *types_;
dvander@54
    54
    StringPool *symbols_;
dvander@61
    55
    GlobalRoots *globalRoots_;
dvander@195
    56
    NativeTable *nativeTable_;
dvander@0
    57
dvander@27
    58
    // Root list.
dvander@28
    59
    Local<Object> *lastLocalRoot_;
dvander@29
    60
    RootScopeManager rootScopes_;
dvander@195
    61
    AutoRooted *specialRoots_;
dvander@27
    62
dvander@69
    63
    // Exception stuff.
dvander@69
    64
    bool hasPendingException_;
dvander@69
    65
    Ke::IMessageReporter *reporter_;
dvander@69
    66
dvander@195
    67
    Ke::IPublicFunctionListener *publicFunctionListener_;
dvander@195
    68
dvander@69
    69
  private:
dvander@69
    70
    Zone *thisFromCtor() {
dvander@69
    71
        return this;
dvander@69
    72
    }
dvander@69
    73
dvander@0
    74
  public:
dvander@11
    75
    Zone();
dvander@54
    76
    ~Zone();
dvander@0
    77
    bool initialize();
dvander@0
    78
dvander@0
    79
    PoolAllocator &pool() {
dvander@0
    80
        return pool_;
dvander@0
    81
    }
dvander@0
    82
    Stack &stack() {
dvander@0
    83
        return stack_;
dvander@0
    84
    }
dvander@11
    85
    Heap &heap() {
dvander@11
    86
        return heap_;
dvander@11
    87
    }
dvander@11
    88
    Object *allocate(MapKind kind, size_t bytes, Heap::Tenure tenure = Heap::Tenure_Default) {
dvander@11
    89
        assert(maps_[kind]);
dvander@53
    90
        Local<Map> map(this, maps_[kind]);
dvander@28
    91
        return allocate(map, bytes, tenure);
dvander@12
    92
    }
dvander@27
    93
    Object *allocate(Handle<Map> map, size_t bytes, Heap::Tenure tenure = Heap::Tenure_Default) {
dvander@27
    94
        return heap_.allocate(map, bytes, tenure);
dvander@27
    95
    }
dvander@53
    96
    Map *getMap(MapKind kind) {
dvander@53
    97
        return maps_[kind];
dvander@53
    98
    }
dvander@12
    99
    Map *allocateMap(MapKind kind, size_t bytes, MapKind parent = MapKind_Meta);
dvander@65
   100
dvander@188
   101
    ArrayMap *externalGenericArrayMap() {
dvander@188
   102
        return externalGenericArrayMap_;
dvander@65
   103
    }
dvander@188
   104
    ArrayMap *externalCharArrayMap() {
dvander@188
   105
        return externalCharArrayMap_;
dvander@65
   106
    }
dvander@65
   107
dvander@11
   108
    TypeManager *types() const {
dvander@11
   109
        return types_;
dvander@11
   110
    }
dvander@195
   111
    AutoRooted **lastSpecialRoot() {
dvander@195
   112
        return &specialRoots_;
dvander@195
   113
    }
dvander@28
   114
    Local<Object> **lastLocalRoot() {
dvander@28
   115
        return &lastLocalRoot_;
dvander@28
   116
    }
dvander@29
   117
    Object **makeScopedRoot(Object *ptr) {
dvander@29
   118
        return rootScopes_.make(ptr);
dvander@29
   119
    }
dvander@29
   120
    RootScopeManager &rootScopes() {
dvander@29
   121
        return rootScopes_;
dvander@29
   122
    }
dvander@54
   123
    StringPool *symbols() {
dvander@54
   124
        return symbols_;
dvander@54
   125
    }
dvander@61
   126
    GlobalRoots *globalRoots() {
dvander@61
   127
        return globalRoots_;
dvander@61
   128
    }
dvander@195
   129
    NativeTable *nativeTable() {
dvander@195
   130
        return nativeTable_;
dvander@195
   131
    }
dvander@54
   132
dvander@54
   133
    String *makeSymbol(const char *string, size_t length);
dvander@54
   134
    String *makeSymbol(const char *string);
dvander@45
   135
dvander@69
   136
    void reportOutOfMemory();
dvander@195
   137
    void reportAllocationOverflow();
dvander@69
   138
    void reportError(Message message, ...);
dvander@69
   139
    void reportErrorVA(Message message, va_list ap);
dvander@69
   140
    bool hasPendingException();
dvander@129
   141
    void clearPendingException();
dvander@69
   142
    void installErrorReporter(Ke::IMessageReporter *reporter);
dvander@69
   143
    void beginCompilationErrors(const char *file);
dvander@69
   144
    void reportCompilationError(Message message, unsigned line, unsigned col, va_list ap);
dvander@69
   145
    void endCompilationErrors();
dvander@69
   146
dvander@45
   147
    void accept(VirtualObjectVisitor *visitor);
dvander@195
   148
dvander@195
   149
    void setPublicFunctionListener(Ke::IPublicFunctionListener *listener);
dvander@195
   150
    void registerPublic(Handle<Function> fun);
dvander@195
   151
    void unregisterPublic(Handle<Function> fun);
dvander@0
   152
};
dvander@0
   153
dvander@0
   154
// In a multi-threaded system, this would be a TLS variable.
dvander@0
   155
// In the interest of simplicity, we are single-threaded for now.
dvander@60
   156
extern Zone *SingleThreadedZone;
dvander@0
   157
dvander@0
   158
static inline Zone *ZONE()
dvander@0
   159
{
dvander@60
   160
    return SingleThreadedZone;
dvander@0
   161
}
dvander@0
   162
dvander@28
   163
template <typename T>
dvander@28
   164
Local<T>::Local(Zone *zone, T *thing)
dvander@28
   165
  : zone_(zone),
dvander@28
   166
    thing_(thing),
dvander@28
   167
    prev_(*zone->lastLocalRoot())
dvander@28
   168
{
dvander@28
   169
    *zone->lastLocalRoot() = toRaw();
dvander@0
   170
}
dvander@0
   171
dvander@28
   172
template <typename T>
dvander@129
   173
dvander@28
   174
Local<T>::~Local()
dvander@28
   175
{
dvander@28
   176
    assert(*zone_->lastLocalRoot() == toRaw());
dvander@28
   177
    *zone_->lastLocalRoot() = prev_;
dvander@28
   178
}
dvander@28
   179
dvander@29
   180
template <typename T>
dvander@53
   181
ScopedRoot<T>::ScopedRoot()
dvander@53
   182
  : thingp_(make(NULL))
dvander@53
   183
{
dvander@53
   184
}
dvander@53
   185
dvander@53
   186
template <typename T>
dvander@29
   187
ScopedRoot<T>::ScopedRoot(T *thing)
dvander@34
   188
  : thingp_(make(thing))
dvander@29
   189
{
dvander@29
   190
    // Type check that T is an Object.
dvander@29
   191
    (void)static_cast<Object *>(thing);
dvander@29
   192
}
dvander@29
   193
dvander@29
   194
template <typename T> T **
dvander@29
   195
ScopedRoot<T>::make(T *other)
dvander@29
   196
{
dvander@29
   197
    return reinterpret_cast<T **>(ZONE()->makeScopedRoot(other));
dvander@29
   198
}
dvander@29
   199
dvander@29
   200
RootScope::RootScope(Zone *zone)
dvander@60
   201
  : Ke::HandleScope(zone, zone->rootScopes().position(), *zone->rootScopes().scope())
dvander@29
   202
{
dvander@29
   203
}
dvander@29
   204
dvander@28
   205
}
dvander@28
   206
dvander@28
   207
#endif // _include_jitcraft_thread_h_