/** -*- tab-width: 4 -*- * This file is part of Erjang - A JVM-based Erlang VM * * Copyright (c) 2010 by Trifork * * 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 erjang.beam.loader; import static erjang.beam.CodeAtoms.START_ATOM; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.EOFException; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.HashMap; import java.util.logging.Level; import java.util.logging.Logger; import erjang.EAtom; import erjang.EBinary; import erjang.EInputStream; import erjang.EObject; import erjang.ESeq; import erjang.EString; import erjang.beam.BeamOpcode; import erjang.beam.repr.AnonFun; import erjang.beam.repr.CodeTables; import erjang.beam.repr.ExtFun; import erjang.beam.repr.FunctionInfo; import erjang.beam.repr.FunctionRepr; import erjang.beam.repr.Insn; import erjang.beam.repr.LineRecord; import erjang.beam.repr.ModuleRepr; import erjang.beam.repr.Operands; import erjang.beam.repr.Operands.AllocList; import erjang.beam.repr.Operands.Atom; import erjang.beam.repr.Operands.BitString; import erjang.beam.repr.Operands.ByteString; import erjang.beam.repr.Operands.DestinationOperand; import erjang.beam.repr.Operands.FReg; import erjang.beam.repr.Operands.Int; import erjang.beam.repr.Operands.Label; import erjang.beam.repr.Operands.Literal; import erjang.beam.repr.Operands.Operand; import erjang.beam.repr.Operands.SelectList; import erjang.beam.repr.Operands.SourceOperand; import erjang.beam.repr.Operands.TableLiteral; import erjang.beam.repr.Operands.XReg; import erjang.beam.repr.Operands.YReg; import erjang.driver.IO; public class BeamLoader extends CodeTables { static Logger log = Logger.getLogger("erjang.beam"); /** For testing purposes. */ public static void main(String[] args) throws IOException { for (String filename : args) read(filename); } public static ModuleRepr read(String filename) throws IOException { long file_size = new File(filename).length(); DataInputStream in = null; try { in = new DataInputStream(new FileInputStream(filename)); BeamLoader bl = new BeamLoader(in, file_size, false); bl.read(); return bl.toModuleRepr(); } finally { if (in != null) in.close(); } } public static ModuleRepr parse(byte[] data) throws IOException { ByteArrayInputStream in = new ByteArrayInputStream(data); BeamLoader bl = new BeamLoader(new DataInputStream(in), data.length, false); bl.read(); return bl.toModuleRepr(); } //====================================================================== private EInputStream in; private boolean include_debug_info; private EObject attributes, compilation_info, abstract_tree; private FunctionInfo[] exports = new FunctionInfo[0], localFunctions = new FunctionInfo[0]; private ArrayList<Insn> code; private ArrayList<FunctionRepr> functionReprs; private EBinary module_md5; //====================================================================== public ModuleRepr toModuleRepr() { FunctionRepr[] functions = new FunctionRepr[functionReprs.size()]; functions = functionReprs.toArray(functions); return new ModuleRepr(this, atom(1), exports, (ESeq)attributes, (ESeq)compilation_info, functions); } //====================================================================== // Magic numbers, file outline: static final int FOR1 = 0x464f5231; // "FOR1" static final int BEAM = 0x4245414d; // "BEAM" static final int GZIP = 0x1f8b0000; // GZIP header // Magic numbers, section headers: static final int ATOM = 0x41746f6d; // "Atom" static final int CODE = 0x436f6465; // "Code" static final int STR_T = 0x53747254; // "StrT" static final int IMP_T = 0x496d7054; // "ImpT" static final int EXP_T = 0x45787054; // "ExpT" static final int LIT_T = 0x4c697454; // "LitT" static final int FUN_T = 0x46756e54; // "FunT" static final int LOC_T = 0x4c6f6354; // "LocT" static final int ATTR = 0x41747472; // "Attr" static final int C_INF = 0x43496e66; // "CInf" static final int ABST = 0x41627374; // "Abst" static final int LINE = 0x4c696e65; // "Line" static final int[] SECTION_READING_ORDER = { ATOM, STR_T, LIT_T, // Have no dependencies. IMP_T, EXP_T, FUN_T, LOC_T, // Function tables - depend on atom table. LINE, CODE, // Depends on virtually all tables. C_INF, ATTR, ABST // Less vital sections. }; // TODO: Take an InputStream instead of a DataInputStream (to avoid overhead when we start out with a ByteArrayInputStream). public BeamLoader(DataInputStream in, long actual_file_size, boolean include_debug_info) throws IOException { this.include_debug_info = include_debug_info; int header; in = new DataInputStream(new java.io.BufferedInputStream(in)); boolean zipped = false; in.mark(8); if ((header=in.readInt()) != FOR1) { if ((header & 0xffff0000) == GZIP) { in.reset(); in = new DataInputStream(new java.util.zip.GZIPInputStream(in)); zipped = true; if (in.readInt() != FOR1) { throw new IOException("Bad header. Not an IFF1 file."); } } else { throw new IOException("Bad header. Not an IFF1 file; header: "+ Integer.toHexString(header)); } } int stated_length = in.readInt(); if (in.readInt() != BEAM) throw new IOException("Bad header. Not a BEAM code file."); if (!zipped && (stated_length+8 != actual_file_size)) throw new IOException("File length is off - stated as "+(stated_length+8)+", is "+actual_file_size); byte[] data = new byte[stated_length-4]; in.readFully(data); this.in = new EInputStream(data); } public void read() throws IOException { /* We want to process sections in an order which avoids forward * references. (For instance, many sections refer to the atoms table, * so we want to process the atoms table early on.) * For this reason, we first create a section directory, then * process the sections in a suitable order. */ final HashMap<Integer,SectionMetadata> section_map = new HashMap<Integer,SectionMetadata>(); for (SectionMetadata smd; (smd = readSectionHeader()) != null; ) { section_map.put(smd.tag, smd); // Skip to next section header: in.setPos(smd.offset + ((smd.length+3)&~3)); } compute_module_md5(section_map); for (int tag : SECTION_READING_ORDER) { SectionMetadata smd = section_map.get(tag); if (smd != null) readSection(smd); } functionReprs = partitionCodeByFunction(); } static final int[] SECTION_MD5_ORDER = { ATOM, CODE, STR_T, IMP_T, EXP_T, }; private void compute_module_md5(final HashMap<Integer, SectionMetadata> section_map) { SectionMetadata smd; MessageDigest context; try { context = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { return; } for (int tag : SECTION_MD5_ORDER) { smd = section_map.get(tag); in.updateMessageDigest(context, smd.offset, smd.length); } if ((smd = section_map.get(FUN_T)) != null) { int start = smd.offset; int left = smd.length; if (left >= 4) { byte[] zero = new byte[4]; in.updateMessageDigest(context, start, 4); start += 4; left -= 4; while (left >= 24) { in.updateMessageDigest(context, start, 20); context.update(zero, 0, 4); start += 24; left -= 24; } } if (left > 0) { in.updateMessageDigest(context, start, left); } } if ((smd = section_map.get(LIT_T)) != null) { in.updateMessageDigest(context, smd.offset, smd.length); } byte[] digest = context.digest(); this.module_md5 = new EBinary(digest); } public ArrayList<FunctionRepr> partitionCodeByFunction() { int funCount = (exports==null?0:exports.length) + (localFunctions==null?0:localFunctions.length); ArrayList<FunctionRepr> functions = new ArrayList<FunctionRepr>(funCount); FunctionInfo fi = null; ArrayList<Insn> currentFunctionBody = null; for (Insn insn : code) { FunctionInfo newFI = null; if (insn.opcode() == BeamOpcode.label) { // We might switch to a new function int labelNr = ((Insn.I)insn).i1; newFI = functionAtLabel(labelNr+1); if (newFI==null) newFI = functionAtLabel(labelNr); } else if (insn.opcode() == BeamOpcode.int_code_end) { newFI = new FunctionInfo(null,null,-1,-1); // Easy way to handle last function } if (newFI != null && newFI != fi) { // Do switch if (fi != null) { // Add previous FunctionRepr fun = new FunctionRepr(fi, currentFunctionBody); functions.add(fun); } fi = newFI; currentFunctionBody = new ArrayList<Insn>(); } // currentFunctionBody and fi are now updated. currentFunctionBody.add(insn); } return functions; } public SectionMetadata readSectionHeader() throws IOException { int tag; try { tag = in.read4BE(); if (log.isLoggable(Level.FINE)) log.fine("Reading section with tag "+toSymbolicTag(tag)+" at "+in.getPos()); } catch (EOFException eof) { return null; } int sectionLength = in.read4BE(); int startPos = in.getPos(); return new SectionMetadata(tag, startPos, sectionLength); } public void readSection(SectionMetadata section) throws IOException { in.setPos(section.offset); // Read section body: try { if (section.length>0) switch (section.tag) { case ATOM: readAtomSection(); break; case CODE: readCodeSection(); break; case STR_T: readStringSection(section.length); break; case IMP_T: readImportSection(); break; case EXP_T: readExportSection(); break; case FUN_T: readFunctionSection(); break; case LIT_T: readLiteralSection(); break; case LOC_T: readLocalFunctionSection(); break; case ATTR: readAttributeSection(); break; case C_INF: readCompilationInfoSection(); break; case ABST: readASTSection(); break; case LINE: readLineSection(); break; default: if (log.isLoggable(Level.WARNING)) log.warning("Unrecognized section tag: "+Integer.toHexString(section.tag)); } // switch } catch (Exception e) { int relPos = in.getPos()-section.offset; try { int curPos = in.getPos(); in.setPos(curPos-16); byte[] d = new byte[64]; int ctxlen = in.read(d); if (log.isLoggable(Level.SEVERE)) { StringBuilder sb = new StringBuilder(); sb.append("Context dump: \n"); for (int i=0; i<ctxlen; i++) { int byt = d[i] & 0xFF; if (byt<16) sb.append("0"); sb.append(Integer.toHexString(byt & 0xFF)); sb.append(" "); if ((i+1) % 16 == 0 || (i+1)==ctxlen) sb.append("\n"); } log.severe(sb.toString()); } } catch (Exception e2) {} throw new IOException("Error occurred around "+relPos+"=0x"+Integer.toHexString(relPos)+" bytes into section "+Integer.toHexString(section.tag), e); } int readLength = in.getPos()-section.offset; if (readLength > section.length) throw new IOException("Malformed section #"+Integer.toHexString(section.tag)+": used "+readLength+" bytes of "+section.length+" (pos="+in.getPos()); // in.setPos(startPos + ((sectionLength+3)&~3)); } /** * @param tag * @return */ private String toSymbolicTag(int tag) { char[] sym = new char[4]; for (int i = 3, d = tag; i >= 0; i--, d >>>= 8) { sym[i] = (char) (d & 0xff); if (!Character.isJavaIdentifierPart(sym[i])) { return "0x" + Integer.toHexString(tag); } } return new String(sym); } public void readLineSection() throws IOException { int version = in.read4BE(); int flags = in.read4BE(); int count = in.read4BE(); int records = in.read4BE(); int fnames = in.read4BE(); /* System.err.println("Line section" +"; version="+version +"; flags="+flags +"; count="+count +"; records="+records +"; fnames="+fnames); */ lineRecords = new LineRecord[records+1]; int fnameNo = 0; for (int i = 0; i < records; ) { int d1 = in.read1(); switch (d1 & 0x07) { case ATOM4_TAG: case ATOM12_TAG: fnameNo = readSmallIntValue(d1); break; default: Int val = readOperand(d1).testInt(); lineRecords[++i] = new LineRecord(fnameNo, val.value); // System.err.println("line["+(i)+"] = "+val.value + " ["+fnameNo+"]"); } } filenames = new EString[fnames+1]; for (int i = 0; i < fnames; i++) { String filename = new String(readBinary(in.read2BE()), IO.UTF8); // System.err.println("file: "+filename); filenames[i+1] = EString.fromString(filename); } } public void readAtomSection() throws IOException { if (log.isLoggable(Level.FINE)) log.fine("readAtomSection"); int nAtoms = in.read4BE(); if (log.isLoggable(Level.FINE)) log.fine("Number of atoms: "+nAtoms); atoms = new EAtom[nAtoms]; for (int i=0; i<nAtoms; i++) { String atom = readString(in.read1()); if (log.isLoggable(Level.FINE)) log.fine("- #"+(i+1)+": '"+atom+"'"); atoms[i] = EAtom.intern(atom); } } public void readStringSection(int sectionLength) throws IOException { if (log.isLoggable(Level.FINE)) log.fine("readStringSection"); stringpool = readBinary(sectionLength); } public void readExportSection() throws IOException { if (log.isLoggable(Level.FINE)) log.fine("readExportSection"); int nExports = in.read4BE(); exports = new FunctionInfo[nExports]; if (log.isLoggable(Level.FINE)) log.fine("Number of exports: "+nExports); EAtom mod = moduleName(); for (int i=0; i<nExports; i++) { int fun_atom_nr = in.read4BE(); int arity = in.read4BE(); int label = in.read4BE(); EAtom fun = atom(fun_atom_nr); exports[i] = new FunctionInfo(mod, fun, arity, label); addFunctionAtLabel(exports[i]); if (log.isLoggable(Level.FINE) && atoms != null) { log.fine("- #"+(i+1)+": "+atom(fun_atom_nr)+"/"+arity+" @ "+label); } } } public void readLocalFunctionSection() throws IOException { if (log.isLoggable(Level.FINE)) log.fine("readLocalFunctionSection"); int nLocals = in.read4BE(); localFunctions = new FunctionInfo[nLocals]; if (log.isLoggable(Level.FINE)) log.fine("Number of locals: "+nLocals); EAtom mod = moduleName(); for (int i=0; i<nLocals; i++) { int fun_atom_nr = in.read4BE(); int arity = in.read4BE(); int label = in.read4BE(); EAtom fun = atom(fun_atom_nr); localFunctions[i] = new FunctionInfo(mod, fun, arity, label); addFunctionAtLabel(localFunctions[i]); if (log.isLoggable(Level.FINE) && atoms != null) { log.fine("- #"+(i+1)+": "+atom(fun_atom_nr)+"/"+arity+" @ "+label); } } } public void readFunctionSection() throws IOException { if (log.isLoggable(Level.FINE)) log.fine("readFunctionSection"); int nFunctions = in.read4BE(); anonymousFuns = new AnonFun[nFunctions]; if (log.isLoggable(Level.FINE)) log.fine("Number of function descrs: "+nFunctions); EAtom mod = moduleName(); for (int i=0; i<nFunctions; i++) { int fun_atom_nr = in.read4BE(); int arity = in.read4BE(); int label = in.read4BE(); int index = in.read4BE(); int free_vars = in.read4BE(); int old_uniq = in.read4BE(); EAtom fun = atom(fun_atom_nr); anonymousFuns[i] = new AnonFun(mod, fun, arity, label, old_uniq, i, module_md5, index, free_vars); if (log.isLoggable(Level.FINE) && atoms != null) { log.fine("- #"+(i+1)+": "+fun+"/"+arity+" @ "+label); log.fine("--> occur:"+index+" free:"+free_vars+" $ "+old_uniq); } } } /** readImportSection * Depends on atom table. */ public void readImportSection() throws IOException { if (log.isLoggable(Level.FINE)) log.fine("readImportSection"); int nImports = in.read4BE(); if (log.isLoggable(Level.FINE)) log.fine("Number of imports: "+nImports); externalFuns = new ExtFun[nImports]; for (int i=0; i<nImports; i++) { int m_atm_no = in.read4BE(); int f_atm_no = in.read4BE(); int arity = in.read4BE(); EAtom mod = atom(m_atm_no), fun = atom(f_atm_no); externalFuns[i] = new ExtFun(mod, fun, arity); if (log.isLoggable(Level.FINE) && atoms != null) { log.fine("- #"+(i+1)+": "+mod+":"+fun+"/"+arity); } } } public void readAttributeSection() throws IOException { if (log.isLoggable(Level.FINE)) log.fine("readAttributeSection"); attributes = in.read_any(); if (log.isLoggable(Level.FINE)) log.fine("Attibutes: "+attributes); } public void readCompilationInfoSection() throws IOException { if (log.isLoggable(Level.FINE)) log.fine("readCompilationInfoSection"); compilation_info = in.read_any(); if (log.isLoggable(Level.FINE)) log.fine("Compilation info: "+compilation_info); } public void readASTSection() throws IOException { if (log.isLoggable(Level.FINE)) log.fine("readASTSection"); if (!include_debug_info) return; abstract_tree = in.read_any(); // if (log.isLoggable(Level.FINE)) log.fine("AST: "+abstract_tree); } public void readLiteralSection() throws IOException { if (log.isLoggable(Level.FINE)) log.fine("readLiteralSection"); final byte[] buf = in.read_size_and_inflate(); final EInputStream is = new EInputStream(buf); int nLiterals = is.read4BE(); if (log.isLoggable(Level.FINE)) log.fine("Number of literals: "+nLiterals); literals = new EObject[nLiterals]; for (int i=0; i<nLiterals; i++) { int lit_length = is.read4BE(); int pos_before_lit = is.getPos(); literals[i] = is.read_any(); if (log.isLoggable(Level.FINE)) log.fine("- #"+i+": "+literals[i]); int pos_after_lit = is.getPos(); assert(pos_after_lit == pos_before_lit + lit_length); } } public void readCodeSection() throws IOException { if (log.isLoggable(Level.FINE)) log.fine("readCodeSection"); int flags = in.read4BE(); // Only 16 ever seen int zero = in.read4BE(); // Only 0 ever seen int highestOpcode = in.read4BE(); int labelCnt = in.read4BE(); int funCnt = in.read4BE(); if (log.isLoggable(Level.FINE)) log.fine("Code metrics: flags:"+flags+ ", z:"+zero+ ", hop:"+highestOpcode+ ", L:"+labelCnt+ ", f:"+funCnt); code = new ArrayList<Insn>(); Insn insn; do { insn = readInstruction(); code.add(insn); } while (insn.opcode() != BeamOpcode.int_code_end); } public Insn readInstruction() throws IOException { int opcode_no = in.read1(); BeamOpcode opcode = BeamOpcode.decode(opcode_no); if (opcode != null) { switch (opcode) { //---------- 0-ary ---------- case K_return: case send: case remove_message: case timeout: case if_end: case int_code_end: case fclearerror: case bs_init_writable: case on_load: return new Insn(opcode); // TODO: use static set of objects //---------- 1-ary ---------- case line: { int i1 = readCodeInteger(); int lineNo = -1; if (lineRecords != null && lineRecords[i1] != null) { lineNo = lineRecords[i1].lineNo; } if (log.isLoggable(Level.FINE)) log.fine("DB| ### line #"+i1+"="+lineNo+"###"); return new Insn.I(BeamOpcode.line, lineNo); } case label: case deallocate: case call_fun: case apply: { int i1 = readCodeInteger(); if (log.isLoggable(Level.FINE) && opcode==BeamOpcode.label) log.fine("DB| ### label "+i1+"###"); return new Insn.I(opcode, i1); } case loop_rec_end: case wait: case jump: case fcheckerror: case recv_mark: case recv_set: { Label lbl = readLabel(); return new Insn.L(opcode, lbl); } case put: case badmatch: case case_end: case try_case_end: { SourceOperand src = readSource(); return new Insn.S(opcode, src); } case init: case bs_context_to_binary: { DestinationOperand dest = readDestination(); return new Insn.D(opcode, dest); } case make_fun2: { int fun_ref = readCodeInteger(); return new Insn.F(opcode, fun_ref, anonFun(fun_ref)); } case try_end: case catch_end: case try_case: { YReg y = readYReg(); return new Insn.Y(opcode, y); } case bs_put_string: { ByteString bin = readBytestringRef(); return new Insn.By(opcode, bin); } //---------- 2-ary ---------- case allocate: case allocate_zero: case trim: case apply_last: { int i1 = readCodeInteger(); int i2 = readCodeInteger(); return new Insn.II(opcode, i1, i2); } case test_heap: { AllocList al = readAllocList(); int i2 = readCodeInteger(); return new Insn.WI(opcode, al, i2); } case call: case call_only: { int i1 = readCodeInteger(); Label label = readLabel(); return new Insn.IL(opcode, i1, label, functionAtLabel(label.nr)); } case call_ext: case call_ext_only: { int i1 = readCodeInteger(); int ext_fun_ref = readCodeInteger(); return new Insn.IE(opcode, i1, extFun(ext_fun_ref)); } case bs_save2: case bs_restore2: { DestinationOperand dest = readDestination(); int i2; if ((peekTag() & 0x7) == ATOM4_TAG) { if (readAtom().getEAtom() != START_ATOM) throw new IOException("integer or 'start' expected"); i2 = -1; } else i2 = readCodeInteger(); return new Insn.DI(opcode, dest, i2, true); } case move: case fmove: case fconv: { SourceOperand src = readSource(); DestinationOperand dest = readDestination(); return new Insn.SD(opcode, src, dest); } case put_tuple: { int i1 = readCodeInteger(); DestinationOperand dest = readDestination(); return new Insn.ID(opcode, i1, dest); } case loop_rec: { Label label = readLabel(); DestinationOperand dest = readDestination(); return new Insn.LD(opcode, label, dest); } case K_try: case K_catch: { YReg y = readYReg(); Label label = readLabel(); return new Insn.YL(opcode, y, label); } case is_map: case is_integer: case is_float: case is_number: case is_atom: case is_pid: case is_reference: case is_port: case is_nil: case is_binary: case is_list: case is_nonempty_list: case is_tuple: case is_function: case is_boolean: case is_bitstr: { Label label = readLabel(); DestinationOperand src = readDestination(); return new Insn.LD(opcode, label, src, true); } case wait_timeout: { Label label = readLabel(); SourceOperand src = readSource(); return new Insn.LS(opcode, label, src, false); } case raise: { SourceOperand src1 = readSource(); SourceOperand src2 = readSource(); return new Insn.SS(opcode, src1, src2); } case put_string: { ByteString bin = readBytestringRef(); DestinationOperand dest = readDestination(); return new Insn.ByD(opcode, bin, dest); } //---------- 3-ary ---------- case allocate_heap: case allocate_heap_zero: { int i1 = readCodeInteger(); AllocList al = readAllocList(); int i3 = readCodeInteger(); return new Insn.IWI(opcode, i1, al, i3); } case func_info: { Atom mod = readAtom(); Atom fun = readAtom(); int arity = readCodeInteger(); return new Insn.AAI(opcode, mod,fun,arity); } case call_ext_last: { int arity = readCodeInteger(); int ext_fun_ref = readCodeInteger(); int dealloc = readCodeInteger(); return new Insn.IEI(opcode, arity, extFun(ext_fun_ref), dealloc); } case put_list: { SourceOperand src1 = readSource(); SourceOperand src2 = readSource(); DestinationOperand dest = readDestination(); return new Insn.SSD(opcode, src1, src2, dest); } case get_tuple_element: { SourceOperand src = readSource(); int i = readCodeInteger(); DestinationOperand dest = readDestination(); return new Insn.SID(opcode, src, i, dest); } case set_tuple_element: { SourceOperand src = readSource(); DestinationOperand dest = readDestination(); int i = readCodeInteger(); return new Insn.SDI(opcode, src, dest, i); } case get_list: { SourceOperand src = readSource(); DestinationOperand dest1 = readDestination(); DestinationOperand dest2 = readDestination(); return new Insn.SDD(opcode, src, dest1, dest2); } case test_arity: case bs_test_tail2: case bs_test_unit: { Label label = readLabel(); DestinationOperand dest = readDestination(); int i1 = readCodeInteger(); return new Insn.LDI(opcode, label, dest, i1, true); } case is_lt: case is_ge: case is_eq: case is_ne: case is_eq_exact: case is_ne_exact: { Label label = readLabel(); SourceOperand src1 = readSource(); SourceOperand src2 = readSource(); return new Insn.LSS(opcode, label, src1, src2, true); } case is_function2: { Label label = readLabel(); DestinationOperand dest = readDestination(); SourceOperand src = readSource(); return new Insn.LDS(opcode, label, dest, src, true); } case fnegate: case bs_utf8_size: case bs_utf16_size: { Label label = readLabel(); SourceOperand src = readSource(); DestinationOperand dest = readDestination(); return new Insn.LSD(opcode, label, src, dest); } case call_last: { int i1 = readCodeInteger(); Label label = readLabel(); int i3 = readCodeInteger(); return new Insn.ILI(opcode, i1, label, i3, functionAtLabel(label.nr)); } case fadd: case fsub: case fmul: case fdiv: { Label label = readLabel(); SourceOperand src1 = readSource(); SourceOperand src2 = readSource(); DestinationOperand dest = readDestination(); return new Insn.LSSD(opcode, label, src1, src2, dest, true); } case bs_add: { Label label = readLabel(); SourceOperand src1 = readSource(); SourceOperand src2 = readSource(); int i3 = readCodeInteger(); DestinationOperand dest = readDestination(); return new Insn.LSSID(opcode, label, src1, src2, i3, dest); } case bs_skip_utf8: case bs_skip_utf16: case bs_skip_utf32: { Label label = readLabel(); DestinationOperand dest = readDestination(); int i3 = readCodeInteger(); int i4 = readCodeInteger(); return new Insn.LDII(opcode, label, dest, i3, i4); } case bs_match_string: { Label label = readLabel(); DestinationOperand dest = readDestination(); BitString bin = readBitstringRef(); return new Insn.LDBi(opcode, label, dest, bin); } case bs_put_utf8: case bs_put_utf16: case bs_put_utf32: { Label label = readLabel(); int i2 = readCodeInteger(); SourceOperand src = readSource(); return new Insn.LIS(opcode, label, i2, src, true); } case bs_start_match2: case bs_get_utf8: case bs_get_utf16: case bs_get_utf32: { Label label = readLabel(); DestinationOperand dest1 = readDestination(); int i3 = readCodeInteger(); int i4 = readCodeInteger(); DestinationOperand dest2 = readDestination(); return new Insn.LDIID(opcode, label, dest1, i3, i4, dest2, opcode != BeamOpcode.bs_start_match2); } case bs_put_integer: case bs_put_float: case bs_put_binary: { Label label = readLabel(); SourceOperand src2 = readSource(); int i3 = readCodeInteger(); int i4 = readCodeInteger(); SourceOperand src5 = readSource(); return new Insn.LSIIS(opcode, label, src2, i3, i4, src5, true); } case bs_init2: case bs_init_bits: { Label label = readOptionalLabel(); SourceOperand src2; if ((peekTag() & 0x7) == CODEINT4_TAG) { int i2 = readCodeInteger(); src2 = new Operands.Int(i2); } else { src2 = readSource(); } int i3 = readCodeInteger(); int i4 = readCodeInteger(); int i5 = readCodeInteger(); DestinationOperand dest = readDestination(); return new Insn.LSIIID(opcode, label, src2, i3, i4, i5, dest, true); } case bs_skip_bits2: { Label label = readLabel(); DestinationOperand dest = readDestination(); SourceOperand src = readSource(); int i3 = readCodeInteger(); int i4 = readCodeInteger(); return new Insn.LDSII(opcode, label, dest, src, i3, i4); } case bs_get_integer2: case bs_get_float2: case bs_get_binary2: { Label label = readLabel(); DestinationOperand dest2 = readDestination(); int i3 = readCodeInteger(); SourceOperand src4 = readSource(); int i5 = readCodeInteger(); int i6 = readCodeInteger(); DestinationOperand dest = readDestination(); return new Insn.LDISIID(opcode, label, dest2, i3, src4, i5, i6, dest); } case bs_append: // LSIIISIS { Label label = readLabel(); SourceOperand src2 = readSource(); int i3 = readCodeInteger(); int i4 = readCodeInteger(); int i5 = readCodeInteger(); SourceOperand src6 = readSource(); int i7 = readCodeInteger(); DestinationOperand dest8 = readDestination(); return new Insn.BSAppend(opcode, label, src2, i3, i4, i5, src6, i7, dest8); } case bs_private_append: // LSISID { Label label = readLabel(); SourceOperand src2 = readSource(); int i3 = readCodeInteger(); SourceOperand src4 = readSource(); int i5 = readCodeInteger(); DestinationOperand dest = readDestination(); return new Insn.BSPrivateAppend(opcode, label, src2, i3, src4, i5, dest); } case select_val: case select_tuple_arity: { SourceOperand src = readSource(); Label defaultLbl = readLabel(); SelectList jumpTable = readSelectList(); return new Insn.Select(opcode, src, defaultLbl, jumpTable); } //---------- BIFs ---------- case bif0: { Label optLabel = readOptionalLabel(); int ext_fun_ref = readCodeInteger(); DestinationOperand dest = readDestination(); return new Insn.Bif(opcode, optLabel, extFun(ext_fun_ref), dest); } case bif1: { Label optLabel = readOptionalLabel(); int ext_fun_ref = readCodeInteger(); SourceOperand arg = readSource(); DestinationOperand dest = readDestination(); return new Insn.Bif(opcode, optLabel, extFun(ext_fun_ref), arg, dest); } case bif2: { Label optLabel = readOptionalLabel(); int ext_fun_ref = readCodeInteger(); SourceOperand arg1 = readSource(); SourceOperand arg2 = readSource(); DestinationOperand dest = readDestination(); return new Insn.Bif(opcode, optLabel, extFun(ext_fun_ref), arg1, arg2, dest); } case gc_bif1: { Label optLabel = readOptionalLabel(); int save = readCodeInteger(); int ext_fun_ref = readCodeInteger(); SourceOperand arg = readSource(); DestinationOperand dest = readDestination(); return new Insn.GcBif(opcode, optLabel, extFun(ext_fun_ref), save, arg, dest); } case gc_bif2: { Label optLabel = readOptionalLabel(); int save = readCodeInteger(); int ext_fun_ref = readCodeInteger(); SourceOperand arg1 = readSource(); SourceOperand arg2 = readSource(); DestinationOperand dest = readDestination(); return new Insn.GcBif(opcode, optLabel, extFun(ext_fun_ref), save, arg1, arg2, dest); } case gc_bif3: { Label optLabel = readOptionalLabel(); int save = readCodeInteger(); int ext_fun_ref = readCodeInteger(); SourceOperand arg1 = readSource(); SourceOperand arg2 = readSource(); SourceOperand arg3 = readSource(); DestinationOperand dest = readDestination(); return new Insn.GcBif(opcode, optLabel, extFun(ext_fun_ref), save, arg1, arg2, arg3, dest); } // map operations case get_map_elements: case has_map_fields: { Label optlabel = readOptionalLabel(); SourceOperand map = readSource(); SelectList mapKeys = readSelectList(); return new Insn.MapQuery(opcode, optlabel, map, mapKeys); } case put_map_assoc: case put_map_exact: { Label optlabel = readOptionalLabel(); SourceOperand src = readSource(); DestinationOperand dest = readDestination(); int _n = readCodeInteger(); SelectList mapKeyValues = readSelectList(); return new Insn.MapUpdate(opcode, optlabel, src, _n, mapKeyValues, dest); } default: throw new IOException("Unknown instruction: "+opcode); } // switch } else throw new IOException("Unknown opcode: 0x"+Integer.toHexString(opcode_no)); } //========== Utility functions ============================== public String readString(int len) throws IOException { return new String(readBinary(len)); } public byte[] readBinary(int len) throws IOException { byte[] data = new byte[len]; in.readFully(data); return data; } //========== Operand tags: //TODO: These need to be cleared up. Look at a bit less. static final int CODEINT4_TAG = 0; static final int INTLIT4_TAG = 1; static final int ATOM4_TAG = 2; static final int XREG4_TAG = 3; static final int YREG4_TAG = 4; static final int LABEL4_TAG = 5; static final int EXTENDED_TAG = 7; static final int CODEINT12_TAG = 8; static final int BIGINT_TAG = 9; static final int ATOM12_TAG = 10; static final int XREG12_TAG = 11; static final int YREG12_TAG = 12; static final int LABEL12_TAG = 13; static final int EXTENDED2_TAG = 7; //========== Extended operand tags: static final int FLOATLIT_TAG2 = 0; static final int SELECTLIST_TAG2 = 1; static final int FLOATREG_TAG2 = 2; static final int ALLOCLIST_TAG2 = 3; static final int LITERAL_TAG2 = 4; int peekTag() throws IOException { return in.peek() & 0x0F; } public SourceOperand readSource() throws IOException { return readOperand().asSource(); } public DestinationOperand readDestination() throws IOException { return readOperand().asDestination(); } public Label readOptionalLabel() throws IOException { return (peekTag() == LABEL4_TAG || peekTag() == LABEL12_TAG) ? readLabel() : null; // 'nofail' } public Label readLabel() throws IOException { return readOperand().asLabel(); } public Literal readLiteral() throws IOException { return readOperand().asLiteral(); } public Atom readAtom() throws IOException { return readOperand().asAtom(); } public BitString readBitstringRef() throws IOException { int bits = readCodeInteger(); int start = readCodeInteger(); return new BitString(bitstring(start,bits)); } public ByteString readBytestringRef() throws IOException { int bytes = readCodeInteger(); int start = readCodeInteger(); return new ByteString(string(start,bytes)); } public SelectList readSelectList() throws IOException { return readOperand().asSelectList(); } public AllocList readAllocList() throws IOException { switch (peekTag()) { case CODEINT4_TAG: case CODEINT12_TAG: { int words = readCodeInteger(); return new AllocList(words); } case EXTENDED_TAG: { return readOperand().asAllocList(); } default: throw new IOException("Expected alloc list, got "+readOperand().toSymbolic()); } // switch } public YReg readYReg() throws IOException { return readOperand().asYReg(); } public int readCodeInteger() throws IOException { int d1 = in.read1(); int tag = d1 & 0x07; if (tag == CODEINT4_TAG) return readSmallIntValue(d1); else throw new IOException("Not a code-int: "+readOperand(d1).toSymbolic()); } public Operand readOperand() throws IOException { int d1 = in.read1(); return readOperand(d1); } public Operand readOperand(int d1) throws IOException { int tag = d1 & 0x07; switch (tag) { case CODEINT4_TAG: return new Operands.CodeInt(readSmallIntValue(d1)); case INTLIT4_TAG: { if ((d1 & 0x8) == 0) return new Operands.Int(readSmallIntValue(d1)); else { // case BIGINT_TAG: int hdata = d1>>4; if ((hdata & 1) == 0) { // Fixed-length return new Operands.Int((hdata << 7) + in.read1()); } else { int len; if (hdata < 15) { // Small var-length len = 2+(hdata>>1); } else { // Big var-length len = 2+(hdata>>1) + readCodeInteger(); } byte d[] = new byte[len]; in.readFully(d); return Operands.makeInt(d); } } } case ATOM4_TAG: case ATOM12_TAG: { int nr = readSmallIntValue(d1); return (nr==0)? Operands.Nil : new Operands.Atom(atom(nr)); } case XREG4_TAG: case XREG12_TAG: { int nr = readSmallIntValue(d1); return XReg.get(nr); } case YREG4_TAG: case YREG12_TAG: { int nr = readSmallIntValue(d1); return YReg.get(nr); } case LABEL4_TAG: case LABEL12_TAG: { int nr = readSmallIntValue(d1); return new Label(nr); } case EXTENDED_TAG: { int moretag = d1>>4; switch (moretag) { case FLOATLIT_TAG2:{ double value = Double.longBitsToDouble(in.readBE(8)); return new Operands.Float(value); } case SELECTLIST_TAG2: { int length = readCodeInteger(); //if(length % 2 != 0) throw new Error("bad select list length: "+length); Operand[] list = new Operand[length]; for (int i=0; i<length; i++) { list[i] = readOperand(); } return new SelectList(list); } case ALLOCLIST_TAG2: { int length = readCodeInteger(); int[] list = new int[2*length]; for (int i=0; i<length; i++) { list[2*i] = readCodeInteger(); list[2*i+1] = readCodeInteger(); } return new AllocList(list); } case FLOATREG_TAG2: { int nr = readSmallIntValue(in.read1()); return new FReg(nr); } case LITERAL_TAG2: { int nr = readSmallIntValue(in.read1()); return new TableLiteral(literal(nr)); } default: log.warning("*** Unhandled extended operand tag: "+moretag); } // switch break; } default: log.warning("*** Unhandled operand tag: "+tag); } // switch return null; } public int readSmallIntValue(int head) throws IOException { int tag = head & 0x0F; int hdata = head>>4; if ((tag & 0x08) == 0) { return hdata; } else if ((hdata & 1) == 0) { // 1 byte more return (hdata<<7) + in.read1(); } else { // >1 bytes more int len = 2+(hdata>>1); byte d[] = new byte[len]; in.readFully(d); BigInteger value = new BigInteger(d); if (len>4 || value.compareTo(BigInteger.ZERO) < 0) throw new IOException("Code integer out of bounds: "+value); else return value.intValue(); } } static class SectionMetadata { final int tag, offset, length; public SectionMetadata(int tag, int offset, int length) { this.tag = tag; this.offset = offset; this.length = length; } } }