All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sandius.rembulan.compiler.LuaCompiler Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2016 Miroslav Janíček
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.sandius.rembulan.compiler;

import net.sandius.rembulan.compiler.analysis.DependencyAnalyser;
import net.sandius.rembulan.compiler.analysis.DependencyInfo;
import net.sandius.rembulan.compiler.analysis.LivenessAnalyser;
import net.sandius.rembulan.compiler.analysis.LivenessInfo;
import net.sandius.rembulan.compiler.analysis.SlotAllocInfo;
import net.sandius.rembulan.compiler.analysis.SlotAllocator;
import net.sandius.rembulan.compiler.analysis.TypeInfo;
import net.sandius.rembulan.compiler.analysis.Typer;
import net.sandius.rembulan.compiler.gen.BytecodeEmitter;
import net.sandius.rembulan.compiler.gen.ClassNameTranslator;
import net.sandius.rembulan.compiler.gen.CompiledClass;
import net.sandius.rembulan.compiler.gen.SuffixingClassNameTranslator;
import net.sandius.rembulan.compiler.gen.asm.ASMBytecodeEmitter;
import net.sandius.rembulan.compiler.tf.BranchInliner;
import net.sandius.rembulan.compiler.tf.CPUAccounter;
import net.sandius.rembulan.compiler.tf.CodeSimplifier;
import net.sandius.rembulan.compiler.tf.ConstFolder;
import net.sandius.rembulan.compiler.tf.DeadCodePruner;
import net.sandius.rembulan.parser.ParseException;
import net.sandius.rembulan.parser.Parser;
import net.sandius.rembulan.parser.TokenMgrError;
import net.sandius.rembulan.parser.analysis.NameResolver;
import net.sandius.rembulan.parser.ast.Chunk;
import net.sandius.rembulan.util.ByteVector;
import net.sandius.rembulan.util.Check;

import java.io.ByteArrayInputStream;
import java.util.ArrayDeque;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * A Lua-to-Java-bytecode compiler.
 */
public class LuaCompiler {

	private final CompilerSettings settings;

	/**
	 * Constructs a new compiler instance with the given settings.
	 *
	 * @param settings  the settings, must not be {@code null}
	 *
	 * @throws NullPointerException  if {@code settings} is {@code null}
	 */
	public LuaCompiler(CompilerSettings settings) {
		this.settings = Objects.requireNonNull(settings);
	}

	/**
	 * Constructs a new compiler instance with
	 * {@linkplain CompilerSettings#defaultSettings() default settings}.
	 */
	public LuaCompiler() {
		this(CompilerSettings.defaultSettings());
	}

	/**
	 * Returns the settings used in this compiler instance.
	 *
	 * @return  the settings used by this compiler
	 */
	public CompilerSettings settings() {
		return settings;
	}

	private static Chunk parse(String sourceText) throws ParseException, TokenMgrError {
		ByteArrayInputStream bais = new ByteArrayInputStream(sourceText.getBytes());
		Parser parser = new Parser(bais);
		return parser.Chunk();
	}

	private static Module translate(Chunk chunk) {
		chunk = NameResolver.resolveNames(chunk);
		return IRTranslator.translate(chunk);
	}

	private Iterable sortTopologically(Module module) {
		// TODO
		return module.fns();
	}

	private IRFunc optimise(IRFunc fn) {
		IRFunc oldFn;

		do {
			oldFn = fn;

			TypeInfo typeInfo = Typer.analyseTypes(fn);

			fn = CPUAccounter.collectCPUAccounting(fn);
			fn = BranchInliner.inlineBranches(fn, typeInfo);

			if (settings.constFolding()) {
				fn = ConstFolder.replaceConstOperations(fn, typeInfo);
				LivenessInfo liveness = LivenessAnalyser.computeLiveness(fn);
				fn = DeadCodePruner.pruneDeadCode(fn, typeInfo, liveness);
			}

			fn = CodeSimplifier.pruneUnreachableCode(fn);
			fn = CodeSimplifier.mergeBlocks(fn);

		} while (!oldFn.equals(fn));

		return fn;
	}

	private static class ProcessedFunc {

		public final IRFunc fn;
		public final SlotAllocInfo slots;
		public final TypeInfo types;
		public final DependencyInfo deps;

		private ProcessedFunc(IRFunc fn, SlotAllocInfo slots, TypeInfo types, DependencyInfo deps) {
			this.fn = Check.notNull(fn);
			this.slots = Check.notNull(slots);
			this.types = Check.notNull(types);
			this.deps = Check.notNull(deps);
		}

	}

	ProcessedFunc processFunction(IRFunc fn) {
		fn = CPUAccounter.insertCPUAccounting(fn);
		fn = optimise(fn);

		SlotAllocInfo slots = SlotAllocator.allocateSlots(fn);
		TypeInfo types = Typer.analyseTypes(fn);
		DependencyInfo deps = DependencyAnalyser.analyse(fn);

		return new ProcessedFunc(fn, slots, types, deps);
	}

	private Iterable processModule(Module m) {
		Map pfs = new HashMap<>();

		for (IRFunc fn : sortTopologically(m)) {
			ProcessedFunc pf = processFunction(fn);
			pfs.put(fn.id(), pf);
		}

		ProcessedFunc main = pfs.get(FunctionId.root());
		assert (main != null);

		Set result = new HashSet<>();
		Deque open = new ArrayDeque<>();

		// only add functions reachable from main
		open.add(main);
		while (!open.isEmpty()) {
			ProcessedFunc pf = open.pop();
			if (!result.contains(pf)) {
				result.add(pf);
				for (FunctionId id : pf.deps.nestedRefs()) {
					open.push(pfs.get(id));
				}
			}
		}

		return result;
	}

	private CompiledClass compileFunction(ProcessedFunc pf, String sourceFileName, String rootClassName) {
		ClassNameTranslator classNameTranslator = new SuffixingClassNameTranslator(rootClassName);
		BytecodeEmitter emitter = new ASMBytecodeEmitter(
				pf.fn, pf.slots, pf.types, pf.deps,
				settings, classNameTranslator,
				sourceFileName);
		return emitter.emit();
	}

	/**
	 * Compiles the Lua source string {@code sourceText} into Java bytecode, giving the main
	 * class the name {@code rootClassName}, and using {@code sourceFileName} as the name
	 * of the source file (for debugging information),
	 *
	 * @param sourceText  source text, must not be {@code null}
	 * @param sourceFileName  file name of the source, must not be {@code null}
	 * @param rootClassName  class name of the main class, must not be {@code null}
	 * @return  {@code sourceText} compiled into a loadable module
	 *
	 * @throws NullPointerException  if {@code sourceText}, {@code sourceFileName}
	 *                               or {@code rootClassName} is {@code null}
	 * @throws TokenMgrError  when {@code sourceText} cannot be lexically analysed following
	 *                        the Lua lexical rules
	 * @throws ParseException  when {@code sourceText} cannot be parsed following the Lua
	 *                         grammar
	 */
	public CompiledModule compile(String sourceText, String sourceFileName, String rootClassName)
			throws ParseException, TokenMgrError {

		Check.notNull(sourceText);
		Chunk ast = parse(sourceText);
		Module module = translate(ast);

		Iterable pfs = processModule(module);

		Map classMap = new HashMap<>();
		String mainClass = null;
		for (ProcessedFunc pf : pfs) {
			CompiledClass cc = compileFunction(pf, sourceFileName, rootClassName);

			if (pf.fn.id().isRoot()) {
				assert (mainClass == null);
				mainClass = cc.name();
			}

			classMap.put(cc.name(), cc.bytes());
		}

		if (mainClass == null) {
			throw new IllegalStateException("Module main class not found");
		}

		return new CompiledModule(Collections.unmodifiableMap(classMap), mainClass);
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy