src/Structures.cpp
author David Anderson <dvander@alliedmods.net>
Sat Nov 17 18:41:01 2012 -0800 (2012-11-17)
changeset 195 c1ba166f3fc4
parent 192 23a804fa1d3e
child 234 26e3035cdffc
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@146
     1
/* vim: set ts=4 sw=4 tw=99 et:
dvander@85
     2
 *
dvander@85
     3
 * Copyright (C) 2012 David Anderson
dvander@85
     4
 *
dvander@146
     5
 * This file is part of SourcePawn.
dvander@85
     6
 *
dvander@146
     7
 * SourcePawn is free software: you can redistribute it and/or modify it under
dvander@85
     8
 * the terms of the GNU General Public License as published by the Free
dvander@85
     9
 * Software Foundation, either version 3 of the License, or (at your option)
dvander@85
    10
 * any later version.
dvander@85
    11
 * 
dvander@146
    12
 * SourcePawn is distributed in the hope that it will be useful, but WITHOUT ANY
dvander@85
    13
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
dvander@85
    14
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
dvander@85
    15
 *
dvander@85
    16
 * You should have received a copy of the GNU General Public License along with
dvander@146
    17
 * SourcePawn. If not, see http://www.gnu.org/licenses/.
dvander@85
    18
 */
dvander@147
    19
#include <string.h>
dvander@85
    20
#include "Structures.h"
dvander@146
    21
#include "Zone.h"
dvander@85
    22
#include "Types.h"
dvander@192
    23
#include "Array.h"
danderson@92
    24
#include "Spaces-inl.h"
danderson@92
    25
#include "Heap-inl.h"
dvander@85
    26
dvander@85
    27
using namespace ke;
dvander@85
    28
dvander@85
    29
StructureBufferBuilder::StructureBufferBuilder()
dvander@85
    30
  : size_(0)
dvander@85
    31
{
dvander@85
    32
}
dvander@85
    33
dvander@85
    34
bool
dvander@85
    35
StructureBufferBuilder::add(Handle<Type> type, unsigned *offset)
dvander@85
    36
{
dvander@85
    37
    *offset = size_;
dvander@85
    38
dvander@85
    39
    if (!type->isTraceable()) {
dvander@85
    40
        // For now, assume the size of primitives is 4-byte.
dvander@85
    41
        assert(type->isInt32() || type->isFloat());
dvander@85
    42
        size_ += 4;
dvander@85
    43
        return true;
dvander@85
    44
    }
dvander@85
    45
dvander@85
    46
    if (!offsets_.append(size_))
dvander@85
    47
        return false;
dvander@85
    48
dvander@85
    49
    size_ += sizeof(Object *);
dvander@85
    50
    return true;
dvander@85
    51
}
dvander@146
    52
dvander@146
    53
Descriptor *
dvander@195
    54
Descriptor::New(Zone *zone, Handle<String> name, Handle<Type> type)
dvander@146
    55
{
dvander@146
    56
    Local<Descriptor> desc(zone,
dvander@146
    57
        Descriptor::cast(zone->allocate(MapKind_Descriptor, sizeof(Descriptor), Heap::Tenure_Old)));
dvander@146
    58
    if (!desc)
dvander@146
    59
        return NULL;
dvander@146
    60
dvander@146
    61
    desc->name_ = name;
dvander@146
    62
    desc->type_ = type;
dvander@195
    63
    desc->offset_ = unsigned(-1);
dvander@146
    64
    return desc;
dvander@146
    65
}
dvander@146
    66
dvander@147
    67
StructMap *
dvander@195
    68
StructMap::New(Zone *zone, const StructureBufferBuilder &builder, Handle<StructType> type)
dvander@147
    69
{
dvander@195
    70
    Vector<unsigned> composedIndexes;
dvander@195
    71
    for (size_t i = 0; i < type->fields()->length(); i++) {
dvander@195
    72
        Descriptor *desc = Descriptor::cast(type->fields()->at(i));
dvander@195
    73
        if (desc->type()->isArray() || desc->type()->isStruct()) {
dvander@195
    74
            if (!composedIndexes.append(i))
dvander@195
    75
                return NULL;
dvander@195
    76
        }
dvander@195
    77
    }
dvander@195
    78
dvander@195
    79
    Local<FixedArray> composed(zone, FixedArray::New(zone, composedIndexes.length(), Heap::Tenure_Old));
dvander@195
    80
    if (!composed)
dvander@195
    81
        return NULL;
dvander@195
    82
dvander@195
    83
    for (size_t i = 0; i < composedIndexes.length(); i++) {
dvander@195
    84
        Descriptor *desc = Descriptor::cast(type->fields()->at(composedIndexes[i]));
dvander@195
    85
        composed->set(zone, i, desc);
dvander@195
    86
    }
dvander@195
    87
dvander@147
    88
    size_t bytesNeeded = sizeof(StructMap) +
dvander@147
    89
                         (builder.traceableOffsetCount() * sizeof(unsigned));
dvander@147
    90
dvander@147
    91
    Local<StructMap> map(zone);
dvander@147
    92
    map = StructMap::cast(zone->allocateMap(MapKind_Struct, bytesNeeded, MapKind_MetaStruct));
dvander@147
    93
    if (!map)
dvander@147
    94
        return NULL;
dvander@147
    95
dvander@147
    96
    map->structureSize_ = builder.structureSize();
dvander@147
    97
    map->traceOffsetCount_ = builder.traceableOffsetCount();
dvander@195
    98
    map->type_ = type;
dvander@153
    99
    map->composed_ = composed;
dvander@147
   100
    memcpy(const_cast<unsigned *>(map->traceOffsets()), builder.offsets(),
dvander@147
   101
           sizeof(unsigned) * builder.traceableOffsetCount());
dvander@147
   102
dvander@147
   103
    return map;
dvander@147
   104
}
dvander@147
   105
dvander@147
   106
Struct *
dvander@147
   107
Struct::New(Zone *zone, Handle<StructMap> map, Heap::Tenure tenure)
dvander@147
   108
{
dvander@147
   109
    size_t bytesNeeded = sizeof(Struct) + map->structureSize();
dvander@147
   110
    Local<Struct> obj(zone, Struct::cast(zone->allocate(map, bytesNeeded, tenure)));
dvander@147
   111
    if (!obj)
dvander@147
   112
        return NULL;
dvander@147
   113
dvander@147
   114
    memset((Struct *)obj + 1, 0, map->structureSize());
dvander@153
   115
dvander@153
   116
    // Initialize any sub-structures. Right now, this is performed as part of
dvander@153
   117
    // struct allocation, rather than inlined in bytecode, for simplicity.
dvander@153
   118
    if (map->composed()) {
dvander@153
   119
        Local<Descriptor> desc(zone);
dvander@192
   120
        Local<Object> child(zone);
dvander@192
   121
        Local<StructMap> childStructMap(zone);
dvander@192
   122
        Local<ArrayMap> childArrayMap(zone);
dvander@192
   123
dvander@153
   124
        for (unsigned i = 0; i < map->composed()->length(); i++) {
dvander@153
   125
            desc = Descriptor::cast(map->composed()->at(i));
dvander@153
   126
dvander@192
   127
            if (desc->type()->isStruct()) {
dvander@195
   128
                childStructMap = StructMap::cast(StructType::cast(desc->type())->newMap());
dvander@192
   129
                child = Struct::New(zone, childStructMap, tenure);
dvander@192
   130
            } else if (desc->type()->isArray()) {
dvander@195
   131
                assert(false);
dvander@195
   132
#if 0
dvander@192
   133
                childArrayMap = ArrayMap::cast(desc->type()->newMap());
dvander@192
   134
                if (desc->type()->isFixedLength())
dvander@192
   135
                    child = Array::NewFixed(zone, childArrayMap, tenure);
dvander@192
   136
                else
dvander@192
   137
                    child = Array::NewEmpty(zone, childArrayMap, tenure);
dvander@195
   138
#endif
dvander@192
   139
            }
dvander@153
   140
            if (!child)
dvander@153
   141
                return NULL;
dvander@153
   142
dvander@153
   143
            obj->set(zone, desc, child);
dvander@153
   144
        }
dvander@153
   145
    }
dvander@153
   146
dvander@147
   147
    return obj;
dvander@147
   148
}
dvander@147
   149