walt-compiler
Version:
Alternative syntax for WebAssembly text format
264 lines (233 loc) • 7.72 kB
JavaScript
// @flow
import invariant from 'invariant';
import Syntax from 'walt-syntax';
import walkNode from 'walt-parser-tools/walk-node';
import { mapNode } from 'walt-parser-tools/map-node';
import mapSyntax from './map-syntax';
import mergeBlock from './merge-block';
import generateElement from './element';
import generateExport from './export';
import generateMemory from './memory';
import generateTable from './table';
import generateInitializer from '../generator/initializer';
import generateImport from './import';
import generateType from './type';
import generateData from './generate-data';
import { generateValueType } from './utils';
import { generateImplicitFunctionType } from './type';
import {
GLOBAL_INDEX,
FUNCTION_INDEX,
FUNCTION_METADATA,
TYPE_INDEX,
AST_METADATA,
} from '../semantics/metadata';
import type { NodeType, GeneratorOptions } from '../flow/types';
import type {
ProgramType,
IntermediateOpcodeType,
IntermediateVariableType,
} from './flow/types';
const DATA_SECTION_HEADER_SIZE = 4;
export const generateCode = (
func: NodeType
): { code: IntermediateOpcodeType[], locals: IntermediateVariableType[] } => {
// eslint-disable-next-line
const [argsNode, resultNode, ...body] = func.params;
const metadata = func.meta[FUNCTION_METADATA];
invariant(body, 'Cannot generate code for function without body');
invariant(metadata, 'Cannot generate code for function without metadata');
const { locals, argumentsCount } = metadata;
const block = {
code: [],
// On this Episode of ECMAScript Spec: Object own keys traversal!
// Sometimes it pays to know the spec. Keys are traversed in the order
// they are added to the object. This includes Object.keys. Because the AST is traversed
// depth-first we can guarantee that arguments will also be added first
// to the locals object. We can depend on the spec providing the keys,
// such that we can slice away the number of arguments and get DECLARED locals _only_.
locals: Object.keys(locals)
.slice(argumentsCount)
.map(key => generateValueType(locals[key])),
debug: `Function ${func.value}`,
};
block.code = body.map(mapSyntax(block)).reduce(mergeBlock, []);
return block;
};
function generator(ast: NodeType, config: GeneratorOptions): ProgramType {
const program: ProgramType = {
Version: config.version,
Types: [],
Start: [],
Element: [],
Code: [],
Exports: [],
Imports: [],
Globals: [],
Functions: [],
Memory: [],
Table: [],
Artifacts: [],
Data: [],
Name: {
module: config.filename,
functions: [],
locals: [],
},
};
let { statics } = ast.meta[AST_METADATA];
if (config.linker != null) {
statics = {
...config.linker.statics,
...statics,
};
}
const { map: staticsMap, data } = generateData(
statics,
DATA_SECTION_HEADER_SIZE
);
if (Object.keys(statics).length > 0) {
program.Data = data;
}
const findTypeIndex = (functionNode: NodeType): number => {
const search = generateImplicitFunctionType(functionNode);
return program.Types.findIndex(t => {
const paramsMatch =
t.params.length === search.params.length &&
t.params.reduce((a, v, i) => a && v === search.params[i], true);
const resultMatch = t.result === search.result;
return paramsMatch && resultMatch;
});
};
const findTableIndex = functionIndex =>
program.Element.findIndex(n => n.functionIndex === functionIndex);
const typeMap = {};
const astWithTypes = mapNode({
[Syntax.Typedef]: (node, _ignore) => {
let typeIndex = program.Types.findIndex(({ id }) => id === node.value);
let typeNode = program.Types[typeIndex];
if (typeNode == null) {
typeIndex = program.Types.length;
program.Types.push(generateType(node));
}
typeNode = {
...node,
meta: { ...node.meta, [TYPE_INDEX]: typeIndex },
};
typeMap[node.value] = { typeIndex, typeNode };
return typeNode;
},
})(
mapNode({
[Syntax.Import]: (node, _) => node,
[Syntax.StringLiteral]: (node, _ignore) => {
const { value } = node;
// Don't replace any statics which are not mapped. For example table
// definitions have StringLiterals, but these literals do not get converted.
if (staticsMap[value] == null) {
return node;
}
return {
...node,
value: String(staticsMap[value]),
Type: Syntax.Constant,
};
},
[Syntax.StaticValueList]: node => {
const { value } = node;
return {
...node,
value: String(staticsMap[value]),
Type: Syntax.Constant,
};
},
})(ast)
);
const nodeMap = {
[Syntax.Typedef]: (_, __) => _,
[Syntax.Export]: node => {
const [nodeToExport] = node.params;
program.Exports.push(generateExport(nodeToExport));
},
[Syntax.ImmutableDeclaration]: node => {
switch (node.type) {
case 'Memory':
program.Memory.push(generateMemory(node));
break;
case 'Table':
program.Table.push(generateTable(node));
break;
}
},
[Syntax.Declaration]: node => {
const globalMeta = node.meta[GLOBAL_INDEX];
if (globalMeta != null) {
program.Globals.push(generateInitializer(node));
}
},
[Syntax.Import]: node => {
program.Imports.push(...generateImport(node));
},
[Syntax.FunctionDeclaration]: node => {
const typeIndex = (() => {
const index = findTypeIndex(node);
if (index === -1) {
// attach to a type index
program.Types.push(generateImplicitFunctionType(node));
return program.Types.length - 1;
}
return index;
})();
const patched = mapNode({
FunctionPointer(pointer) {
const metaFunctionIndex = pointer.meta[FUNCTION_INDEX];
const functionIndex = metaFunctionIndex;
let tableIndex = findTableIndex(functionIndex);
if (tableIndex < 0) {
tableIndex = program.Element.length;
program.Element.push(generateElement(functionIndex));
}
return pointer;
},
})(node);
// Quick fix for shifting around function indices. These don't necessarily
// get written in the order they appear in the source code.
const index = node.meta[FUNCTION_INDEX];
invariant(index != null, 'Function index must be set');
program.Functions[index] = typeIndex;
// We will need to filter out the empty slots later
program.Code[index] = generateCode(patched);
if (patched.value === 'start') {
program.Start.push(index);
}
if (config.encodeNames) {
program.Name.functions.push({
index,
name: node.value,
});
const functionMetadata = node.meta[FUNCTION_METADATA];
if (
functionMetadata != null &&
Object.keys(functionMetadata.locals).length
) {
program.Name.locals[index] = {
index,
locals: Object.entries(functionMetadata.locals).map(
([name, local]: [string, any]) => {
return {
name,
index: Number(local.meta['local/index']),
};
}
),
};
}
}
},
};
walkNode(nodeMap)(astWithTypes);
// Unlike function indexes we need function bodies to be exact
program.Code = program.Code.filter(Boolean);
return program;
}
export default generator;