fourthTier: We should have a reduced FTL LLVM pipeline tool in the repository
authoroliver@apple.com <oliver@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 25 Jul 2013 04:04:47 +0000 (04:04 +0000)
committeroliver@apple.com <oliver@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 25 Jul 2013 04:04:47 +0000 (04:04 +0000)
https://bugs.webkit.org/show_bug.cgi?id=118647

Rubber stamped by Geoffrey Garen and Mark Hahnenberg.

Add a tool that takes in an LLVM bitcode file and JITs it in exactly the same
way that the FTL would.

Also add a tool that combines multiple LLVM modules generated by FTL into a
single module.

* ReducedFTL: Added.
* ReducedFTL/ReducedFTL.c: Added.
(usage):
(currentTime):
(MemorySection):
(mmAllocateCodeSection):
(mmAllocateDataSection):
(mmApplyPermissions):
(mmDestroy):
(symbolLookupCallback):
(main):
* ReducedFTL/build.sh: Added.
* ReducedFTL/combineModules.rb: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@153275 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Tools/ChangeLog
Tools/ReducedFTL/ReducedFTL.c [new file with mode: 0644]
Tools/ReducedFTL/build.sh [new file with mode: 0755]
Tools/ReducedFTL/combineModules.rb [new file with mode: 0755]

index 22071bf..387fcd6 100644 (file)
@@ -1,3 +1,30 @@
+2013-07-16  Filip Pizlo  <fpizlo@apple.com>
+
+        fourthTier: We should have a reduced FTL LLVM pipeline tool in the repository
+        https://bugs.webkit.org/show_bug.cgi?id=118647
+
+        Rubber stamped by Geoffrey Garen and Mark Hahnenberg.
+
+        Add a tool that takes in an LLVM bitcode file and JITs it in exactly the same
+        way that the FTL would.
+        
+        Also add a tool that combines multiple LLVM modules generated by FTL into a
+        single module.
+
+        * ReducedFTL: Added.
+        * ReducedFTL/ReducedFTL.c: Added.
+        (usage):
+        (currentTime):
+        (MemorySection):
+        (mmAllocateCodeSection):
+        (mmAllocateDataSection):
+        (mmApplyPermissions):
+        (mmDestroy):
+        (symbolLookupCallback):
+        (main):
+        * ReducedFTL/build.sh: Added.
+        * ReducedFTL/combineModules.rb: Added.
+
 2013-06-09  Filip Pizlo  <fpizlo@apple.com>
 
         Unreviewed, fix minor goof in profiling output layout. We weren't accounting
diff --git a/Tools/ReducedFTL/ReducedFTL.c b/Tools/ReducedFTL/ReducedFTL.c
new file mode 100644 (file)
index 0000000..9a05c21
--- /dev/null
@@ -0,0 +1,405 @@
+/*
+ * Copyright (C) 2013 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+
+/*
+ * Simple tool that takes some LLVM bitcode as input and "JITs" it, in the
+ * same way that the FTL would use LLVM to JIT the IR that it generates.
+ * This is meant for use as a reduction when communicating to LLVMers
+ * about bugs, and for quick "what-if" testing to see how our optimization
+ * pipeline performs. Because of its use as a reduction, this tool is
+ * intentionally standalone and it would be great if it continues to fit
+ * in one file.
+ */
+
+#include <getopt.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include <llvm-c/Analysis.h>
+#include <llvm-c/BitReader.h>
+#include <llvm-c/Core.h>
+#include <llvm-c/Disassembler.h>
+#include <llvm-c/ExecutionEngine.h>
+#include <llvm-c/Target.h>
+#include <llvm-c/Transforms/PassManagerBuilder.h>
+#include <llvm-c/Transforms/Scalar.h>
+
+static void usage()
+{
+    printf("Usage: ReducedFTL <file1> [<file2> ...]\n");
+    printf("\n");
+    printf("Options:\n");
+    printf("--verbose        Display more information, including module dumps.\n");
+    printf("--timing         Measure the time it takes to compile.\n");
+    printf("--disassemble    Disassemble all of the generated code at the end.\n");
+    printf("--mode <mode>    Set the optimization mode (either \"simple\" or \"opt\").\n");
+    printf("--contexts <arg> Set the number of contexts (either \"one\" or \"many\").\n");
+    printf("--help           Print this message.\n");
+    printf("\n");
+    printf("Unless you specify one of --verbose, --timing, or --disassemble, you will\n");
+    printf("not see any output.\n");
+    exit(1);
+}
+
+static double currentTime()
+{
+    struct timeval now;
+    gettimeofday(&now, 0);
+    return now.tv_sec + now.tv_usec / 1000000.0;
+}
+
+struct MemorySection {
+    uint8_t *start;
+    size_t size;
+    struct MemorySection *next;
+};
+
+static struct MemorySection* sectionHead;
+
+static uint8_t *mmAllocateCodeSection(
+    void *opaqueState, uintptr_t size, unsigned alignment, unsigned sectionID)
+{
+    size_t pageSize = getpagesize();
+    
+    uint8_t *start = mmap(
+        0, (size + pageSize - 1) & ~pageSize, 
+        PROT_WRITE | PROT_READ | PROT_EXEC,
+        MAP_ANON | MAP_PRIVATE, -1, 0);
+    if (start == (uint8_t*)-1) {
+        fprintf(stderr, "Unable to allocate %" PRIuPTR " bytes of executable memory.\n", size);
+        exit(1);
+    }
+    
+    struct MemorySection *section = malloc(sizeof(struct MemorySection));
+    section->start = start;
+    section->size = size;
+    section->next = sectionHead;
+    sectionHead = section;
+
+    return start;
+}
+
+static uint8_t *mmAllocateDataSection(
+    void *opaqueState, uintptr_t size, unsigned alignment, unsigned sectionID,
+    LLVMBool isReadOnly)
+{
+    return mmAllocateCodeSection(opaqueState, size, alignment, sectionID);
+}
+
+static LLVMBool mmApplyPermissions(void *opaque, char **message)
+{
+    return 0;
+}
+
+static void mmDestroy(void *opaque)
+{
+}
+
+static const char *symbolLookupCallback(
+    void *opaque, uint64_t referenceValue, uint64_t *referenceType, uint64_t referencePC,
+    const char **referenceName)
+{
+    static char symbolString[20];
+    
+    switch (*referenceType) {
+    case LLVMDisassembler_ReferenceType_InOut_None:
+        return 0;
+    case LLVMDisassembler_ReferenceType_In_Branch:
+        *referenceName = 0;
+        *referenceType = LLVMDisassembler_ReferenceType_InOut_None;
+        snprintf(
+            symbolString, sizeof(symbolString), "0x%lx",
+            (unsigned long)referenceValue);
+        return symbolString;
+    default:
+        fprintf(stderr, "Unexpected reference type!\n");
+        exit(1);
+        return 0;
+    }
+}
+
+int main(int c, char **v)
+{
+    LLVMContextRef *contexts;
+    LLVMModuleRef *modules;
+    char *error;
+    const char *mode = "opt";
+    const char **filenames;
+    unsigned numFiles;
+    unsigned i;
+    bool moreOptions;
+    static int verboseFlag = 0;
+    static int timingFlag = 0;
+    static int disassembleFlag = 0;
+    bool manyContexts = true;
+    double beforeAll;
+    
+    if (c == 1)
+        usage();
+    
+    moreOptions = true;
+    while (moreOptions) {
+        static struct option longOptions[] = {
+            {"verbose", no_argument, &verboseFlag, 1},
+            {"timing", no_argument, &timingFlag, 1},
+            {"disassemble", no_argument, &disassembleFlag, 1},
+            {"mode", required_argument, 0, 0},
+            {"contexts", required_argument, 0, 0},
+            {"help", no_argument, 0, 0}
+        };
+        
+        int optionIndex;
+        int optionValue;
+        
+        optionValue = getopt_long(c, v, "", longOptions, &optionIndex);
+        
+        switch (optionValue) {
+        case -1:
+            moreOptions = false;
+            break;
+            
+        case 0: {
+            const char* thisOption = longOptions[optionIndex].name;
+            if (!strcmp(thisOption, "help"))
+                usage();
+            if (!strcmp(thisOption, "contexts")) {
+                if (!strcasecmp(optarg, "one"))
+                    manyContexts = false;
+                else if (!strcasecmp(optarg, "many"))
+                    manyContexts = true;
+                else {
+                    fprintf(stderr, "Invalid argument for --contexts.\n");
+                    exit(1);
+                }
+                break;
+            }
+            if (!strcmp(thisOption, "mode")) {
+                mode = strdup(optarg);
+                break;
+            }
+            break;
+        }
+            
+        case '?':
+            exit(0);
+            break;
+            
+        default:
+            printf("optionValue = %d\n", optionValue);
+            abort();
+            break;
+        }
+    }
+    
+    LLVMLinkInMCJIT();
+    LLVMInitializeNativeTarget();
+    LLVMInitializeX86AsmPrinter();
+    LLVMInitializeX86Disassembler();
+
+    filenames = (const char **)(v + optind);
+    numFiles = c - optind;
+    
+    contexts = malloc(sizeof(LLVMContextRef) * numFiles);
+    modules = malloc(sizeof(LLVMModuleRef) * numFiles);
+    
+    if (manyContexts) {
+        for (i = 0; i < numFiles; ++i)
+            contexts[i] = LLVMContextCreate();
+    } else {
+        LLVMContextRef context = LLVMContextCreate();
+        for (i = 0; i < numFiles; ++i)
+            contexts[i] = context;
+    }
+    
+    for (i = 0; i < numFiles; ++i) {
+        LLVMMemoryBufferRef buffer;
+        const char* filename = filenames[i];
+        
+        if (LLVMCreateMemoryBufferWithContentsOfFile(filename, &buffer, &error)) {
+            fprintf(stderr, "Error reading file %s: %s\n", filename, error);
+            exit(1);
+        }
+        
+        if (LLVMParseBitcodeInContext(contexts[i], buffer, modules + i, &error)) {
+            fprintf(stderr, "Error parsing file %s: %s\n", filename, error);
+            exit(1);
+        }
+        
+        LLVMDisposeMemoryBuffer(buffer);
+        
+        if (verboseFlag) {
+            printf("Module #%u (%s) after parsing:\n", i, filename);
+            LLVMDumpModule(modules[i]);
+        }
+    }
+
+    if (verboseFlag)
+        printf("Generating code for modules...\n");
+    
+    if (timingFlag)
+        beforeAll = currentTime();
+    for (i = 0; i < numFiles; ++i) {
+        LLVMModuleRef module;
+        LLVMExecutionEngineRef engine;
+        struct LLVMMCJITCompilerOptions options;
+        LLVMValueRef value;
+        LLVMPassManagerRef functionPasses = 0;
+        LLVMPassManagerRef modulePasses = 0;
+        
+        double before;
+        
+        if (timingFlag)
+            before = currentTime();
+        
+        module = modules[i];
+
+        LLVMInitializeMCJITCompilerOptions(&options, sizeof(options));
+        options.OptLevel = 2;
+        options.EnableFastISel = 0;
+        options.MCJMM = LLVMCreateSimpleMCJITMemoryManager(
+            0, mmAllocateCodeSection, mmAllocateDataSection, mmApplyPermissions, mmDestroy);
+    
+        if (LLVMCreateMCJITCompilerForModule(&engine, module, &options, sizeof(options), &error)) {
+            fprintf(stderr, "Error building MCJIT: %s\n", error);
+            exit(1);
+        }
+    
+        if (!strcasecmp(mode, "simple")) {
+            modulePasses = LLVMCreatePassManager();
+            LLVMAddTargetData(LLVMGetExecutionEngineTargetData(engine), modulePasses);
+            LLVMAddConstantPropagationPass(modulePasses);
+            LLVMAddInstructionCombiningPass(modulePasses);
+            LLVMAddPromoteMemoryToRegisterPass(modulePasses);
+            LLVMAddBasicAliasAnalysisPass(modulePasses);
+            LLVMAddTypeBasedAliasAnalysisPass(modulePasses);
+            LLVMAddGVNPass(modulePasses);
+            LLVMAddCFGSimplificationPass(modulePasses);
+            LLVMRunPassManager(modulePasses, module);
+        } else if (!strcasecmp(mode, "opt")) {
+            LLVMPassManagerBuilderRef passBuilder;
+
+            passBuilder = LLVMPassManagerBuilderCreate();
+            LLVMPassManagerBuilderSetOptLevel(passBuilder, 2);
+            LLVMPassManagerBuilderSetSizeLevel(passBuilder, 0);
+        
+            functionPasses = LLVMCreateFunctionPassManagerForModule(module);
+            modulePasses = LLVMCreatePassManager();
+        
+            LLVMAddTargetData(LLVMGetExecutionEngineTargetData(engine), modulePasses);
+        
+            LLVMPassManagerBuilderPopulateFunctionPassManager(passBuilder, functionPasses);
+            LLVMPassManagerBuilderPopulateModulePassManager(passBuilder, modulePasses);
+        
+            LLVMPassManagerBuilderDispose(passBuilder);
+        
+            LLVMInitializeFunctionPassManager(functionPasses);
+            for (value = LLVMGetFirstFunction(module); value; value = LLVMGetNextFunction(value))
+                LLVMRunFunctionPassManager(functionPasses, value);
+            LLVMFinalizeFunctionPassManager(functionPasses);
+        
+            LLVMRunPassManager(modulePasses, module);
+        } else {
+            fprintf(stderr, "Bad optimization mode: %s.\n", mode);
+            fprintf(stderr, "Valid modes are: \"simple\" or \"opt\".\n");
+            exit(1);
+        }
+
+        if (verboseFlag) {
+            printf("Module #%d (%s) after optimization:\n", i, filenames[i]);
+            LLVMDumpModule(module);
+        }
+    
+        for (value = LLVMGetFirstFunction(module); value; value = LLVMGetNextFunction(value)) {
+            if (LLVMIsDeclaration(value))
+                continue;
+            LLVMGetPointerToGlobal(engine, value);
+        }
+
+        if (functionPasses)
+            LLVMDisposePassManager(functionPasses);
+        if (modulePasses)
+            LLVMDisposePassManager(modulePasses);
+    
+        LLVMDisposeExecutionEngine(engine);
+        
+        if (timingFlag) {
+            double after = currentTime();
+            printf("Module #%d (%s) took %lf ms.\n", i, filenames[i], (after - before) * 1000);
+        }
+    }
+    if (timingFlag) {
+        double after = currentTime();
+        printf("Compilation took a total of %lf ms.\n", (after - beforeAll) * 1000);
+    }
+    
+    if (disassembleFlag) {
+        LLVMDisasmContextRef disassembler;
+        struct MemorySection *section;
+        
+        disassembler = LLVMCreateDisasm("x86_64-apple-darwin", 0, 0, 0, symbolLookupCallback);
+        if (!disassembler) {
+            fprintf(stderr, "Error building disassembler.\n");
+            exit(1);
+        }
+    
+        for (section = sectionHead; section; section = section->next) {
+            printf("Disassembly for section %p:\n", section);
+        
+            char pcString[20];
+            char instructionString[1000];
+            uint8_t *pc;
+            uint8_t *end;
+        
+            pc = section->start;
+            end = pc + section->size;
+        
+            while (pc < end) {
+                snprintf(
+                    pcString, sizeof(pcString), "0x%lx",
+                    (unsigned long)(uintptr_t)pc);
+            
+                size_t instructionSize = LLVMDisasmInstruction(
+                    disassembler, pc, end - pc, (uintptr_t)pc,
+                    instructionString, sizeof(instructionString));
+            
+                if (!instructionSize)
+                    snprintf(instructionString, sizeof(instructionString), ".byte 0x%02x", *pc++);
+                else
+                    pc += instructionSize;
+            
+                printf("    %16s: %s\n", pcString, instructionString);
+            }
+        }
+    }
+    
+    return 0;
+}
+
diff --git a/Tools/ReducedFTL/build.sh b/Tools/ReducedFTL/build.sh
new file mode 100755 (executable)
index 0000000..26363ea
--- /dev/null
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+set -e
+set -x
+
+if test "x${LLVM_PATH}" == "x"
+then
+    configPath="llvm-config"
+else
+    configPath="${LLVM_PATH}/bin/llvm-config"
+fi
+
+clang -c -o ReducedFTL.o ReducedFTL.c `${configPath} --cppflags --cflags`
+clang++ -o ReducedFTL ReducedFTL.o -stdlib=libc++ `${configPath} --ldflags --libs`
diff --git a/Tools/ReducedFTL/combineModules.rb b/Tools/ReducedFTL/combineModules.rb
new file mode 100755 (executable)
index 0000000..105d6fb
--- /dev/null
@@ -0,0 +1,204 @@
+#!/usr/bin/env ruby
+
+# Copyright (C) 2013 Apple Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+# THE POSSIBILITY OF SUCH DAMAGE.
+
+class Reference
+    attr_reader :kind, :number
+    
+    def initialize(kind, number)
+        @kind = kind
+        @number = number
+    end
+    
+    def resolve(hashTable, bangTable)
+        case @kind
+        when "#"
+            result = hashTable[@number]
+        when "!"
+            result = bangTable[@number]
+        else
+            raise
+        end
+        raise unless result
+        result
+    end
+end
+
+def parse(string)
+    result = []
+    until string.empty?
+        before, match, string = string.partition(/[!#]([0-9]+)/)
+        result << before
+        if match.empty?
+            result << string
+            break
+        end
+        result << Reference.new(match[0..0], match[1..-1].to_i)
+    end
+    result
+end
+
+class MetaData
+    attr_reader :index, :name, :parent
+    
+    def initialize(index, name, parent)
+        @index = index
+        @name = name
+        @parent = parent
+    end
+end
+
+$definitions = []
+$declarations = {}
+$attributes = []
+$metaData = {}
+$attributesBackMap = {}
+$count = 0
+
+loop {
+    line = $stdin.readline
+    break if line =~ /^define/
+}
+
+# Loop over all definitions.
+shouldContinue = true
+while shouldContinue
+    # We're starting a new definition.
+    body = ""
+    loop {
+        line = $stdin.readline
+        break if line.chomp == "}"
+        body += line
+    }
+    
+    body = parse(body)
+    
+    declarations=[]
+    metaDataMap=[]
+    attributeMap = []
+    unresolvedMetaData = []
+    
+    loop {
+        line = $stdin.gets
+        
+        if not line
+            shouldContinue = false
+            break
+        elsif line =~ /^define/
+            break
+        elsif line =~ /^declare/
+            declarations << parse(line)
+        elsif line =~ /!([0-9]+) = metadata !{metadata !\"([a-zA-Z0-9_]+)\"}/
+            index = $1.to_i
+            name = $2
+            unless $metaData[name]
+                $metaData[name] = MetaData.new($metaData.size, name, nil)
+            end
+            metaDataMap[index] = $metaData[$2].index
+        elsif line =~ /!([0-9]+) = metadata !{metadata !\"([a-zA-Z0-9_]+)\", metadata !([0-9]+)/
+            metaData = MetaData.new($1.to_i, $2, $3.to_i)
+            unresolvedMetaData << metaData
+        elsif line =~ /attributes #([0-9]+) = /
+            attributeNumber = $1.to_i
+            attributeBody = $~.post_match
+            if $attributesBackMap[attributeBody]
+                attributeMap[attributeNumber] = $attributesBackMap[attributeBody]
+            else
+                attributeMap[attributeNumber] = $attributes.size
+                $attributesBackMap[attributeBody] = $attributes.size
+                $attributes << attributeBody
+            end
+        end
+    }
+    
+    # Iteratively resolve meta-data references
+    until unresolvedMetaData.empty?
+        index = 0
+        while index < unresolvedMetaData.size
+            metaData = unresolvedMetaData[index]
+            if $metaData[metaData.name]
+                metaDataMap[metaData.index] = $metaData[metaData.name].index
+                unresolvedMetaData[index] = unresolvedMetaData[-1]
+                unresolvedMetaData.pop
+            elsif metaDataMap[metaData.parent]
+                metaDataMap[metaData.index] = $metaData.size
+                $metaData[metaData.name] = MetaData.new($metaData.size, metaData.name, metaDataMap[metaData.parent])
+                unresolvedMetaData[index] = unresolvedMetaData[-1]
+                unresolvedMetaData.pop
+            else
+                index += 1
+            end
+        end
+    end
+    
+    # Output the body with all of the things remapped.
+    puts "define i64 @jsBody_#{$count += 1}(i64) {"
+    body.each {
+        | thing |
+        if thing.is_a? Reference
+            print(thing.kind + thing.resolve(attributeMap, metaDataMap).to_s)
+        else
+            print(thing)
+        end
+    }
+    puts "}"
+    
+    # Figure out what to do with declarations.
+    declarations.each {
+        | declaration |
+        declaration = declaration.map {
+            | thing |
+            if thing.is_a? Reference
+                thing.kind + thing.resolve(attributeMap, metaDataMap).to_s
+            else
+                thing
+            end
+        }
+        declaration = declaration.join('')
+        
+        next if $declarations[declaration]
+        
+        $declarations[declaration] = true
+    }
+end
+
+$declarations.each_key {
+    | declaration |
+    puts declaration
+}
+
+$attributes.each_with_index {
+    | attribute, index |
+    puts "attributes ##{index} = #{attribute}"
+}
+
+$metaData.each_value {
+    | metaData |
+    print "!#{metaData.index} = metadata !{metadata !\"#{metaData.name}\""
+    if metaData.parent
+        print ", metadata !#{metaData.parent}"
+    end
+    puts "}"
+}
+