src/comment
author David Anderson <dvander@alliedmods.net>
Tue Dec 25 16:32:06 2012 -0800 (2012-12-25)
changeset 238 e9bec7549c28
parent 195 c1ba166f3fc4
permissions -rw-r--r--
Preparatory refactoring for const types.
dvander@195
     1
Massive refactoring to support better type systems. Read on for more.
dvander@195
     2
dvander@195
     3
The previous compiler had a simple pipeline, best outlined as:
dvander@195
     4
  (1) Parsing and name binding -> AST
dvander@195
     5
  (2) Storage allocation -> AST
dvander@195
     6
  (3) Semantic Analysis (SemA) -> AST
dvander@195
     7
  (4) Bytecode Compilation (BC) -> Code
dvander@195
     8
dvander@195
     9
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. 
dvander@195
    10
dvander@195
    11
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).
dvander@195
    12
dvander@195
    13
This patch rewrites most of Keima's backend. Of interest are the following changes:
dvander@195
    14
  (1) CompileContext has been refactored around future multi-file support.
dvander@195
    15
  (2) Parsing no longer performs any name binding.
dvander@195
    16
  (3) The grammar for type-and-name has been changed from:
dvander@195
    17
       (Label | Identifier)? Identifier
dvander@195
    18
      to:
dvander@195
    19
       Type? Identifier
dvander@195
    20
      where:
dvander@195
    21
       Type ::= OldType | NewType
dvander@195
    22
       OldType ::= Label
dvander@195
    23
       NewType ::= Identifier (. NewType)*
dvander@195
    24
                
dvander@195
    25
dvander@195
    26
      Although we do not implement the full production for Type yet, this
dvander@195
    27
      distinction is important. A type identifier is now affixed to the AST
dvander@195
    28
      as an Expression (right now, always a NameProxy), so it can fully
dvander@195
    29
      participate in name binding.
dvander@195
    30
dvander@195
    31
  (4) Immediately after parsing, the AST goes through a NamePopulation phase.
dvander@195
    32
      This phase creates Scope objects for every scope which declares a name,
dvander@195
    33
      and if those names are declaring types or functions, a Symbol is created
dvander@195
    34
      and entered into the scope. Symbols entirely replace the old BoundName
dvander@195
    35
      classes.
dvander@195
    36
dvander@195
    37
  (5) After NamePopulation comes NameBinding. This phase performs three steps:
dvander@195
    38
      (a) Links Scope objects together, to form a tree.
dvander@195
    39
      (b) Binds any free name to an existing name in the scope chain.
dvander@195
    40
      (c) Creates and registers any Symbols for names which have not yet been
dvander@195
    41
          added to the scope (for example, local variables).
dvander@195
    42
dvander@195
    43
      In accordance, all "allocation" and "binding" concepts have been removed
dvander@195
    44
      from Scopes, which are now lightweight container classes.
dvander@195
    45
dvander@195
    46
  (6) SemA now generates bytecode for each statement in the AST. Expression
dvander@195
    47
      compilation is performed via a separate mechanism called HIR (High-level
dvander@195
    48
      Intermediate Representation). To analyze an expression, SemA will walk
dvander@195
    49
      the AST, and produce a HIR object for each node. HIR is typed, and SemA
dvander@195
    50
      may insert coercion nodes, or even expand an AST node into multiple HIR
dvander@195
    51
      nodes. The result of evaluating an expression in SemA is therefore the
dvander@195
    52
      the root of a HIR tree, which SemA then sends to the HIRTranslator, which
dvander@195
    53
      performs bytecode generation.
dvander@195
    54
dvander@195
    55
      As before, SemA still performs full semantic analysis. However, not it
dvander@195
    56
      also produces each function's bytecode.
dvander@195
    57
dvander@195
    58
      This split allows us to decompose potentially complex semantics into
dvander@195
    59
      a more fine-grained, AST-like structure, which can have very simple
dvander@195
    60
      code-generation logic. For example, (1.0 + 5) might look like:
dvander@195
    61
dvander@195
    62
         HAdd(HFloat(1.0), HConvert(HInteger(5), <Float>))
dvander@195
    63
dvander@195
    64
      As part of this decomposition, many opcodes have been removed. Rather than
dvander@195
    65
      using typed jumps, we now expand jumps into longer tests. For example:
dvander@195
    66
         jge.f <label>
dvander@195
    67
      becomes:
dvander@195
    68
         ge.f
dvander@195
    69
         cvt.f2b
dvander@195
    70
         jt <label>
dvander@195
    71
dvander@195
    72
      This simplifies the pipeline, and JITs should be able to melt the added
dvander@195
    73
      work away.
dvander@195
    74
dvander@195
    75
      HIR is not used at a statement level, and HIR does not have any concept
dvander@195
    76
      of control-flow.
dvander@195
    77
dvander@195
    78
  (7) The old BytecodeCompiler has been removed, as its work is now split
dvander@195
    79
      between SemA and HIRTranslator.
dvander@195
    80
dvander@195
    81
  
dvander@195
    82
In addition, some minor refactorings have taken place:
dvander@195
    83
dvander@195
    84
  (1) Opcodes.tbl no longer hardcodes numbers (aaah).
dvander@195
    85
  (2) Type has been split into smaller, typed structs.
dvander@195
    86
  (3) BytecodeEmitter no longer relies on Pools or RootScopes.
dvander@195
    87
  (4) SemA is now responsible for variable/storage allocation.
dvander@195
    88
  (5) Native table are now global, rather than per-module. As such, native
dvander@195
    89
      declarations now result in a Native object (which still requires a
dvander@195
    90
      CALLNATIVE opcode), which is an index into the global table. Natives
dvander@195
    91
      must be bound globally. This is in preparation for module support.
dvander@195
    92
  (6) Publics are no longer tracked, but rather registered via a global
dvander@195
    93
      callback upon module load. This callback can be set by embedders. This
dvander@195
    94
      is in preparation for multi-file support.
dvander@195
    95
  (7) The BytecodeEmitter now performs Symbol allocations itself, and it
dvander@195
    96
      also generates Code objects itself, which removes a good deal of
dvander@195
    97
      complexity.
dvander@195
    98
dvander@195
    99
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@195
   100
dvander@195
   101