blob: ffbcb0c03a063f1f038ea802506838c84e16d705 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
/**
* @author Intel, Evgueni Brevnov, Ivan Volosyuk
*/
#ifndef _LIL_H_
#define _LIL_H_
#include <stdio.h>
#include "open/types.h"
#include "vm_core_types.h"
#include "tl/memory_pool.h"
#include "m2n.h"
/* This file describes LIL - the architecture-independent language used to generate code stubs etc.
Goals:
* Lightweight - i.e. small language, quality code generation possible with small simple algorithms
* Remove most architecture dependencies from code
* (Future) allow inlining of VM stubs into JITed code
* (Future) allow inlining of GC or Thread/Sync code via LIL
* (Possibly future) be the lowest ir of a simple JIT
Design constaints:
* Keep language syntax LL(1) so that parsing can be done with a recursive descent parser
* Make single-pass register allocation possible
* Allow easy experimentation with calling conventions
The basic unit of LIL is a code stub. It is intended to be the entry point of a function that can be
called by managed or native code, and executes in the presence of an activation frame. The frame consists
of up to six parts: inputs, standard places, an m2n frame, locals, outputs, and a return variable. Which
parts are present in the activation frame, and how big each part is, can vary across points in the code stub.
A LIL code stub is an entry and a sequence of statements. The entry specifies the inputs and their types, the
number of standard places, the type of the return, and the calling convention (which determines where the inputs,
return address, and return goes, which registers are caller save vs callee save, and who pops the inputs; standard
places are determined by LIL and the platform, and are intended for trampolines). An entry can have an "arbitrary"
signature, meaning that the number and type of inputs and type of return is unknown (the stub must work for all
possibilities). After entry the declared number
of input variables are available, the declared number of standard places are available, there is no m2n frame,
there are no locals, there are no outputs, and there is no return variable.
cs ::= entry i:cc:sig; ss
ss ::= s | s ss
The statements are:
s ::= :l;
| locals i;
| std_places i;
| alloc v,i;
| v=uop o;
| v=o op o;
| v=o;
| v=ts;
| handles=o;
| ld v,addr(,zx|,sx)?;
| st addr,o;
| inc addr;
| cas addr=o,o,l;
| j l;
| jc cond,l;
| out cc:sig;
| in2out cc:RT;
| calli o;
| ret;
| push_m2n i oh;
| m2n_save_all;
| pop_m2n;
| print str, o;
uop ::= neg | not | sx1 | sx2 | sx4 | zx1 | zx2 | zx4 | ...
op ::= + | - | * | & | << | ...
addr ::= [ov osv i :T ackrel]
ov ::= | v+
osv ::= | 1*v+ | 2*v+ | 4*v+ | 8*v+
acqrel ::= | ,acquire | ,release
cond ::= v=0 | v!=0 | o=o | o!=o | o<o | o<=o | o<u o | o<=u o | ...
calli ::= call | call.noret | tailcall
oh ::= | , handles
o ::= (v | i) (:t)?
cc ::= platform | jni | managed | rth | stdcall
platform = default calling convention used by VM
jni = calling convention used by JNI
managed = calling convention used by jitted code
rth = calling convention used to call runtime helpers
stdcall = Windows's stdcall calling convention
l is a label (sequence of alpha, digit, _, starting with alpha or _)
v is a variable, one of: i0, i1, ... (input variables), sp0, sp1, ... (standard places), l0, l1, ... (local variables),
o0, o1, ... (output variables), r (return variable)
i is an immediate (sequence of digits, or 0x hex digits)
str is either a string enclosed in single quotes, or a number denoting an address
Operand semantics:
An operand with a v stands for that variable; an operand with an i stands for that immediate; an operand with an optional :t is cast to the
type t (ie it has type t). Only certain casts are allowed by the verifier: the types ref, pint, and g4/g8 (depending upon architecture) are
castable to each other. Note that casting refs to/from the other types may be GC unsafe.
Statement semantics:
:l declares a label l. A LIL code stub is not valid if such a label merges control flow with inconsistent
activation frames. Specifically, the number and types of inputs must be the same (currently there is no way they
could not be), only the minimum of the number of standard places will be available after the statement, the m2n frame
must either be present in all incoming edges or absent in all incoming edges, the number of locals must be the same, the
number of outputs must be the same, and
the return variable will be defined after the statement only if it is defined on all incoming edges.
locals i makes i locals available in the activation frame. If there were locals available previously, they are lost.
std_places i makes i standard places available in the activation frame. If there were standard places available previously,
they are lost.
alloc v, i allocates i contigous bytes of space whose lifetime is up to the next ret, tailcall, or pop_m2n instruction.
The address of this
space is placed into v. The space will not be moved for any reason. The amount of space allocated by such instructions must
be statically determinable for each code point in a code stub. This instruction may not appear between out/in2out and
call/call.noret/tailcall instructions.
v=uop o evaluates o to a value, performs unary operation uop on it, and stores the result in v.
v=o1 op o2 evaluates o1 & o2 to values, performs binary operation op on them, and stores the result in v.
v=ts places a pointer to the current thread's thread structure into v.
handles=o evaluates o to a value and sets the handles pointer in the m2n frame to this value. This instruction must be
dominated by a push_m2n handles instruction.
ld v,addr evaluates addr to an address (see below) and loads the value (of type given in addr) at this address into v. If ,zx appears
at the end then the value is zero extended into v, which gets type pint; if ,sx appears at the end then the value is sign extended into v,
which gets pint; otherwise, v gets the same type as in addr.
st addr,o evaluates addr to an address (see below) o to a value and stores the value into the contents (of type given in addr) at the address.
inc addr evaluates addr to an address (ses below) and increments the value (of type given in addr) at this address.
cas addr=o1,o2,l evaluates addr to an address (see below) a, o1 to a value v1, and o2 to a value v2. Then it atomically: compares
the value (of type gien in addr) to o1 and if equal swaps the contents of a with o2. If the match fails then it jumps to label l.
ov+osv+i:T acqrel evaluates to (0 | contents of v) + (0 | 1 * contents of v | 2 * contents of v | 4 * contents of v |
8 * contents of v) + i. Further if acqrel is acquire then the operation in which it appears has acquire semantics, and if
it is release then the operation in which it appears has release semantics.
j l jumps to label l. A LIL code stub is invalid if l is not a declared label in the code stub.
jc cond, l either continues with the next statement or jumps to label l, depending upon the evaluation of cond. A LIL
code stub is invalid if l is not declared in the stub. The conditions are evaluated as follows. v=0 will branch if v
has value 0; v!=0 will branch if v does not have value 0; o1=o2 will branch if o1 & o2 evaluate to the same value; o1!=o2
will branch if o1 & o2 evaluate to different values; o1<o2 will branch if o1 evaluates to a value signed less than what
o2 evaluates to; o1<=o2 will branch if o1 evaluates to a value signed less than or equal what
o2 evaluates to; o1<u o2 will branch if o1 evaluates to a value unsigned less than what
o2 evaluates to; o1<=u o2 will branch if o1 evaluates to a value unsigned less than or equal what
o2 evaluates to.
out cc:Ts:RT sets up in the activation frame the given number of outputs with the given types, cc says what calling
convention will be used, and RT declares the return type. This statement preserves the inputs, standard places, locals,
return, alloced memory, and m2n frame, but destroys the outputs.
in2out cc:RT sets up outputs equal in number and type to the inputs, copies the inputs to the outputs, cc says what calling
convention will be used, and maybe different from the code stub's calling convention; the return type is given by RT.
This statement preserves the inputs, standard places, locals, return, and m2n frame, but redefines the outputs. An in2out
is not allowed if the entry signature is arbitrary.
call o evaluates o to an address and calls it. A LIL code stub is invalid unless call o is dominated by an out statement
or is dominated by an in2out statement. The calling convention declared in the dominating statement is used for this call.
This statement preserves inputs, locals, alloced memory, and the m2n frame, but destroys the standard places, outputs, and
redefines the return to be the value returned from the call.
call.noret o is similar to call o, but the LIL write is asserting the the function never returns. The LIL code generator
is free to not generate code to deal with the function returning.
tailcall o is equivalent to in2out cc:RT;call o;ret; where cc is the code stub's calling convention and RT is the code stub's
return type, except that the LIL code generator might optimise it, and the entry signature may be arbitrary.
ret returns from the code stub. A LIL code stub is invalid if the return is not defined and the return type of the code stub
is not void, or if there is an m2n frame. The calling convention of the code stub determines how the return is done. A return
will automatically free locals, outputs, memory allocated by alloc, and standard places. A return is not allowed if the entry
signature is arbitrary.
push_m2n i,h adds an m2n frame to the activation frame. A LIL code stub is invalid if there is already an m2n frame or if an
alloc could be performed before push_m2n. This instruction preserves inputs, standard places, and locals, but destroys outputs
and the return. The optional handles keyword specifies if the m2n frame will have handles or not. The i parameter specifies the
method associated with the M2nFrame (or 0 for no association).
m2n_save_all saves all callee saves registers in the m2n frame. On some architectures not all such registers are saved on a push m2n,
just the ones that are relevant to most m2n operations. See the M2n module for details. This statement must be dominated by a push_m2n
and it preserves the context.
pop_m2n removes the m2n frame from the activation frame. This statement must be dominated by a push_m2n instruction and with a
consistent handles declaration. If handles is declared then pop_m2n will free all handles attached to this m2n frame. The instruction
preserves inputs, locals, and the return, but destorys standard places, alloc-ed memory, and the outputs.
print outputs a message to the screen in debug mode, and does nothing in
release mode. Its two arguments will be passed to a printf-like function,
with the usual semantics.
A LIL code stub is invalid if it does not end in a terminal instruction, that is, a ret, j, call.noret, or tailcall.
The types are:
T ::= g1 | g2 | g4 | g8 | f4 | f8 | ref | pint
RT ::= T | void
sig ::= Ts:RT | arbitrary
Ts ::= | T,Ts
The meaning of the types is: g1 means a one-byte general-purpose (ie not floating point) quantitiy, g2 means two-byte, g4
means 4-bytes, g8 menas eight-byte, f4 means a 4-byte floating-point quantity, f8 means 8-byte, ref means a pointer into the
managed heap, pint means a platform-sized general-purpose
quantity which might be an integer or an unmanaged pointer, void means there is nothing returned.
The LIL syntax allows comments that start with // and go to the end of the line.
*/
// Datatypes
struct LilCodeStub;
struct LilInstruction;
class LilBb;
struct LilSig;
enum LilVariableKind { LVK_In, LVK_StdPlace, LVK_Out, LVK_Local, LVK_Ret };
enum LilOperation {
LO_Mov, LO_Add, LO_Sub, LO_SgMul, LO_Shl, LO_And, LO_Neg, LO_Not, LO_Sx1, LO_Sx2, LO_Sx4,
LO_Zx1, LO_Zx2, LO_Zx4
};
enum LilPredicate {
LP_IsZero, LP_IsNonzero, LP_Eq, LP_Ne, LP_Le, LP_Lt, LP_Ule, LP_Ult
};
enum LilAcqRel { LAR_None, LAR_Acquire, LAR_Release };
enum LilLdX { LLX_None, LLX_Zero, LLX_Sign };
enum LilCallKind { LCK_Call, LCK_CallNoRet, LCK_TailCall };
enum LilType { LT_G1, LT_G2, LT_G4, LT_G8, LT_F4, LT_F8, LT_Ref, LT_PInt, LT_Void };
enum LilCc { LCC_Platform, LCC_Managed, LCC_Rth, LCC_Jni, LCC_StdCall };
struct LilVariable {
enum LilVariableKind tag;
unsigned index;
};
struct LilOperand {
bool is_immed;
union {
LilVariable var;
POINTER_SIZE_INT imm;
} val;
bool has_cast;
LilType t;
};
typedef const char* LilLabel;
//*** Creators
// Parse a string into code stub, the string can contain % constructs to refer to addition parameters
// Current the % syntax is: %ds where d is a decimal number that starts at 0 and increments by 1 through the string
// and s is a specifier. The following specifiers are allowed in the following circumstances:
// i in an immediate, takes the next argument as a pointer-size int (int on IA32, long long on IPF) as the value of the immediate
// i in a variable, takes the next argument as a platform int as the index (not valid for ret)
// m in a signature, takes the next argument as a Method_Handle and constructs the standard managed signature for it
// j in a signature, takes the next argument as a Method_Handle and constructs the standard JNI signature for it
// % may also be used in a label to get generated labels, there are no numbers, but the specifiers are:
// g in a label, generates a label
// p in a label, use the previously generated label
// n in a label, use the next generated label
// o in a label, use the next next generated label
// Return NULL on parsing error
LilCodeStub* lil_parse_code_stub(const char* src, ...);
// Parse a string as a sequence of instructions and add them to the end of the input code stub.
// The % syntax above can be used.
// Return NULL on parsing error and the input is freed, otherwise the return is the same as the parameter.
LilCodeStub* lil_parse_onto_end(LilCodeStub*, const char* src, ...);
//*** Instruction Contexts
enum LilM2nState { LMS_NoM2n, LMS_NoHandles, LMS_Handles };
struct LilInstructionContext;
VMEXPORT unsigned lil_ic_get_num_std_places(LilInstructionContext*);
VMEXPORT LilM2nState lil_ic_get_m2n_state(LilInstructionContext*);
VMEXPORT unsigned lil_ic_get_num_locals(LilInstructionContext*);
VMEXPORT LilType lil_ic_get_local_type(LilInstructionContext*, unsigned);
VMEXPORT LilSig* lil_ic_get_out_sig(LilInstructionContext*);
VMEXPORT LilType lil_ic_get_ret_type(LilInstructionContext*);
VMEXPORT unsigned lil_ic_get_amt_alloced(LilInstructionContext*);
VMEXPORT LilType lil_ic_get_type(LilCodeStub*, LilInstructionContext*, LilOperand*);
// returns the type of an instruction's destination variable _after_ the
// instruction is performed.
// If the instruction has no destination, LT_Void is returned.
// Notice that this type may be different than the type returned by
// lil_ic_get_type for the destination variable, since the latter
// refers to the situation _before_ the instruction.
VMEXPORT LilType lil_instruction_get_dest_type(LilCodeStub*, LilInstruction*,
LilInstructionContext*);
//*** Interogators
// Is a LIL code stub valid?
VMEXPORT bool lil_is_valid(LilCodeStub* cs);
// Return a code stubs entry signature
VMEXPORT LilSig* lil_cs_get_sig(LilCodeStub* cs);
// Return the number of instructions in a code stub
VMEXPORT unsigned lil_cs_get_num_instructions(LilCodeStub* cs);
// Return the number of BBs in a code stub
VMEXPORT unsigned lil_cs_get_num_BBs(LilCodeStub *cs);
// Return the first BB in the stub (from that one can get all BBs using get_next()
VMEXPORT LilBb* lil_cs_get_entry_BB(LilCodeStub* cs);
// Compute the contexts of each instruction in the code stub
// This function must be called to use instruction iterators that track contexts
VMEXPORT void lil_compute_contexts(LilCodeStub* cs);
// Return maximum number of std places used in the stub
VMEXPORT unsigned lil_cs_get_max_std_places(LilCodeStub * cs);
// Return maximum number of locals used in the stub
VMEXPORT unsigned lil_cs_get_max_locals(LilCodeStub * cs);
// Return size of generated code
VMEXPORT void lil_cs_set_code_size(LilCodeStub * cs, size_t size);
// Return size of generated code
VMEXPORT size_t lil_cs_get_code_size(LilCodeStub * cs);
// totally artificial limit; increase if needed
// the max number of predecessors any BB may have
#define MAX_BB_PRED 10
class LilBb {
private:
// the code stub
LilCodeStub *cs;
// since bb code will appear consecutively in the stub,
// all we need is the first and last instruction
LilInstruction *start;
LilInstruction *end; // if end is NULL, the end of the stub is implied
// the LIL context right before the first instruction of the BB
LilInstructionContext* ctxt;
// successors of this BB in the flowgraph
LilBb* fallthru;
LilBb* branch_target;
// predecessors of this BB in the flowgraph
unsigned num_pred;
LilBb *pred[MAX_BB_PRED];
// for forming BB lists
LilBb* next;
// ID; within each stub, IDs will be unique and go from 0 to <num BBs>-1
int id;
// private constructor; create BBs by calling init_fg()
LilBb(LilCodeStub *_cs, LilInstruction *_start,
LilInstructionContext* ctxt_at_start);
// private operator new; create BBs using new_bb() only
void* operator new(size_t sz, tl::MemoryPool& m);
//MVM
//add empty destructor to avoid warning
void operator delete(void * UNREF obj, tl::MemoryPool& UNREF m){
//do nothing
};
public:
// the only way to create new BBs; create a whole FG for a stub
VMEXPORT static void init_fg(LilCodeStub*);
// sets the last instruction of the BB
VMEXPORT void set_last(LilInstruction *i);
// gets the first instruction
VMEXPORT LilInstruction* get_first();
// gets the last instruction
VMEXPORT LilInstruction* get_last();
// get the context right before the start of the BB
VMEXPORT LilInstructionContext* get_context();
// does this bb contain instruction i?
VMEXPORT bool contains(LilInstruction *i);
// get the label of this BB; NULL if no label exists
VMEXPORT LilLabel get_label();
// set a fallthrough successor to this bb
VMEXPORT void set_fallthru(LilBb *);
// set a branch-target successor to this bb
VMEXPORT void set_branch_target(LilBb *);
// get the fallthrough and branch target successors;
// either of them can be NULL if they don't exist
VMEXPORT LilBb* get_fallthru();
VMEXPORT LilBb* get_branch_target();
// gets the i'th predecessor (NULL if i >= num_pred)
VMEXPORT LilBb *get_pred(unsigned i);
// gets the next BB in the list
VMEXPORT LilBb *get_next();
// returns whether this BB ends in a return instruction
// (tailcall implies return!)
VMEXPORT bool is_ret();
// the id of this BB
VMEXPORT int get_id();
// true if this BB contains calls (which means it may throw exceptions)
VMEXPORT bool does_calls();
// true if this BB ends with a call.noret (which means it is probably cold code)
VMEXPORT bool does_call_noret();
// find a BB with the specified label; NULL if no such bb exists
VMEXPORT static LilBb* get_by_label(LilCodeStub *cs, LilLabel l);
// find a BB which contains the specified instruction; NULL if no such BB exists
VMEXPORT static LilBb* get_by_instruction(LilCodeStub *cs, LilInstruction *i);
// print a BB to a stream (does not print the BB's instructions)
VMEXPORT void print(FILE* out);
};
// Iterate over the instructions in a code stub
class LilInstructionIterator {
public:
// Create an iterator from a code stub, if track_contexts then track the context prior to each instruction
VMEXPORT LilInstructionIterator(LilCodeStub*, bool track_contexts);
// Create an iterator for a BB in a code stub.
// NOTE: track_contexts is currently NOT supported for this type of iterator
VMEXPORT LilInstructionIterator(LilCodeStub*, LilBb*, bool track_contexts);
// Is iterator past the end of the instructions?
VMEXPORT bool at_end();
// Return current instruction
VMEXPORT LilInstruction* get_current();
// Return context prior to current instruction, must have created iterator to track contexts
VMEXPORT LilInstructionContext* get_context();
// Goto the next instruction
VMEXPORT void goto_next();
private:
LilCodeStub* cs;
LilBb *bb; // NULL, unless this is an iterator for a BB
LilInstruction* cur;
bool track_ctxt;
LilInstructionContext* ctxt;
};
// Visitor pattern for instructions
class LilInstructionVisitor {
protected:
VMEXPORT LilInstructionVisitor();
public:
virtual void label(LilLabel) = 0;
virtual void locals(unsigned) = 0;
virtual void std_places(unsigned) = 0;
virtual void alloc(LilVariable*, unsigned) = 0;
virtual void asgn(LilVariable*, enum LilOperation, LilOperand*, LilOperand*) = 0;
virtual void ts(LilVariable*) = 0;
virtual void handles(LilOperand*) = 0;
virtual void ld(LilType t, LilVariable* dst, LilVariable* base, unsigned scale, LilVariable* index, POINTER_SIZE_SINT offset, LilAcqRel, LilLdX) = 0;
virtual void st(LilType t, LilVariable* base, unsigned scale, LilVariable* index, POINTER_SIZE_SINT offset, LilAcqRel, LilOperand* src) = 0;
virtual void inc(LilType t, LilVariable* base, unsigned scale, LilVariable* index, POINTER_SIZE_SINT offset, LilAcqRel) = 0;
virtual void cas(LilType t, LilVariable* base, unsigned scale, LilVariable* index, POINTER_SIZE_SINT offset, LilAcqRel,
LilOperand* cmp, LilOperand* src, LilLabel) = 0;
virtual void j(LilLabel) = 0;
virtual void jc(LilPredicate, LilOperand*, LilOperand*, LilLabel) = 0;
virtual void out(LilSig*) = 0;
virtual void in2out(LilSig*) = 0;
virtual void call(LilOperand*, LilCallKind) = 0;
virtual void ret() = 0;
virtual void push_m2n(Method_Handle method, frame_type current_frame_type, bool handles) = 0;
virtual void m2n_save_all() = 0;
virtual void pop_m2n() = 0;
virtual void print(char *, LilOperand *) = 0;
};
// LilInstructionVisitor_Default provides empty implementations for the
// abstract functions in LilInstructionVisitor. It is a convenient base
// class for scanners that need to act only on a few instructions.
class LilInstructionVisitor_Default : public LilInstructionVisitor {
protected:
LilInstructionVisitor_Default():
LilInstructionVisitor() {}
public:
void label(LilLabel) {}
void locals(unsigned) {}
void std_places(unsigned) {}
void alloc(LilVariable*, unsigned) {}
void asgn(LilVariable*, enum LilOperation, LilOperand*, LilOperand*) {}
void ts(LilVariable*) {}
void handles(LilOperand*) {}
void ld(LilType UNREF t, LilVariable* UNREF dst, LilVariable* UNREF base, unsigned UNREF scale, LilVariable* UNREF index, POINTER_SIZE_SINT UNREF offset, LilAcqRel, LilLdX) {}
void st(LilType UNREF t, LilVariable* UNREF base, unsigned UNREF scale, LilVariable* UNREF index, POINTER_SIZE_SINT UNREF offset, LilAcqRel, LilOperand* UNREF src) {}
void inc(LilType UNREF t, LilVariable* UNREF base, unsigned UNREF scale, LilVariable* UNREF index, POINTER_SIZE_SINT UNREF offset, LilAcqRel) {}
void cas(LilType UNREF t, LilVariable* UNREF base, unsigned UNREF scale, LilVariable* UNREF index, POINTER_SIZE_SINT UNREF offset, LilAcqRel,
LilOperand* UNREF cmp, LilOperand* UNREF src, LilLabel) {}
void j(LilLabel) {}
void jc(LilPredicate, LilOperand*, LilOperand*, LilLabel) {}
void out(LilSig*) {}
void in2out(LilSig*) {}
void call(LilOperand*, LilCallKind) {}
void ret() {}
void push_m2n(Method_Handle UNREF method, frame_type UNREF current_frame_type, bool UNREF handles) {}
void m2n_save_all() {}
void pop_m2n() {}
void print(char *, LilOperand *) {}
};
// Visit instruction using a visitor
VMEXPORT void lil_visit_instruction(LilInstruction*, LilInstructionVisitor*);
// Return variable's kind
VMEXPORT LilVariableKind lil_variable_get_kind(LilVariable* v);
// Return variable's index
VMEXPORT unsigned lil_variable_get_index(LilVariable* v);
// Are two variables the same
VMEXPORT bool lil_variable_is_equal(LilVariable* v1, LilVariable* v2);
// Is operand an immediate?
VMEXPORT bool lil_operand_is_immed(LilOperand* o);
// If operand is an immediate return the immediate value
VMEXPORT POINTER_SIZE_INT lil_operand_get_immed(LilOperand* o);
// If operand is not an immediate return the variable it represents
VMEXPORT LilVariable* lil_operand_get_variable(LilOperand* o);
// Return a signature's calling convention
VMEXPORT LilCc lil_sig_get_cc(LilSig* sig);
// Return whether this signature is arbitrary
VMEXPORT bool lil_sig_is_arbitrary(LilSig *sig);
// Return the number of arguments for a signature (0 if arbitrary)
VMEXPORT unsigned lil_sig_get_num_args(LilSig* sig);
// Return the num-th argument type of a signature (zero based) (undefined if signature is arbitrary)
VMEXPORT LilType lil_sig_get_arg_type(LilSig* sig, unsigned num);
// Return the return type of a signature
VMEXPORT LilType lil_sig_get_ret_type(LilSig* sig);
// Is an operation binary?
VMEXPORT bool lil_operation_is_binary(enum LilOperation op);
// Is a predicate binary?
VMEXPORT bool lil_predicate_is_binary(enum LilPredicate c);
// Is a predicate signed?
VMEXPORT bool lil_predicate_is_signed(LilPredicate c);
//*** Printers
// Print a type to a stream
VMEXPORT void lil_print_type(FILE*, LilType);
// Print a signature to a stream
VMEXPORT void lil_print_sig(FILE*, LilSig*);
// Print an instruction to a stream
VMEXPORT void lil_print_instruction(FILE*, LilInstruction* i);
// Print a code stub to a stream
VMEXPORT void lil_print_code_stub(FILE*, LilCodeStub* cs);
// Print just the entry of a code stub to a stream
VMEXPORT void lil_print_entry(FILE*, LilCodeStub* cs);
//*** Freers
// Return all resources associated with a code stub
// Post: code stub no longer usable
VMEXPORT void lil_free_code_stub(LilCodeStub* cs);
//*** Function pointer utilities
VMEXPORT GenericFunctionPointer lil_npc_to_fp(NativeCodePtr);
#endif // _LIL_H_