issue #60 JIT compile GETTABLE_S

pull/81/head
Dibyendu Majumdar 9 years ago
parent 8b640c25fd
commit f04c5febd6

@ -0,0 +1,38 @@
#include "lua_hdr.h"
#define LUA_TSTRING 4
#define LUA_TSHRSTR (LUA_TSTRING | (0 << 4))
#define checktag(o,t) (rttype(o) == (t))
#define ctb(t) ((t) | BIT_ISCOLLECTABLE)
#define cast(t, exp) ((t)(exp))
#define lmod(s,size) \
(cast(int, (s) & ((size)-1)))
#define ttisshrstring(o) checktag((o), ctb(LUA_TSHRSTR))
#define gco2ts(o) cast(struct TString *, (o))
#define val_(o) ((o)->value_)
#define tsvalue(o) gco2ts(val_(o).gc)
#define gnode(t,i) (&(t)->node[i])
#define gval(n) (&(n)->i_val)
#define gkey(n) cast(const struct TValue*, (&(n)->i_key.tvk))
#define twoto(x) (1<<(x))
#define sizenode(t) (twoto((t)->lsizenode))
#define hashpow2(t,n) (gnode(t, lmod((n), sizenode(t))))
#define hashstr(t,str) hashpow2(t, (str)->hash)
#define eqshrstr(a,b) ((a) == (b))
extern const struct TValue *luaH_getstr (struct Table *t, struct TString *key);
const struct TValue *ravi_getstr (struct Table *t, struct TString *key) {
struct Node *n = hashstr(t, key);
const struct TValue *k = gkey(n);
if (ttisshrstring(k) && eqshrstr(tsvalue(k), key))
return gval(n); /* that's it */
return luaH_getstr(t, key);
}

@ -0,0 +1,75 @@
; ModuleID = 'inline_getstr.c'
target datalayout = "e-m:x-p:32:32-i64:64-f80:32-n8:16:32-S32"
target triple = "i686-pc-windows-gnu"
%struct.TValue = type { %union.Value, i32 }
%union.Value = type { i64 }
%struct.Table = type { %struct.GCObject*, i8, i8, i8, i8, i32, %struct.TValue*, %struct.Node*, %struct.Node*, %struct.Table*, %struct.GCObject*, %struct.RaviArray }
%struct.Node = type { %struct.TValue, %union.TKey }
%union.TKey = type { %struct.anon.1 }
%struct.anon.1 = type { %union.Value, i32, i32 }
%struct.GCObject = type { %struct.GCObject*, i8, i8 }
%struct.RaviArray = type { i8*, i32, i32, i32 }
%struct.TString = type { %struct.GCObject*, i8, i8, i8, i8, i32, %union.anon.2 }
%union.anon.2 = type { i64 }
; Function Attrs: nounwind
define %struct.TValue* @ravi_getstr(%struct.Table* %t, %struct.TString* %key) #0 {
entry:
%hash = getelementptr inbounds %struct.TString, %struct.TString* %key, i32 0, i32 5
%0 = load i32, i32* %hash, align 4, !tbaa !1
%lsizenode = getelementptr inbounds %struct.Table, %struct.Table* %t, i32 0, i32 4
%1 = load i8, i8* %lsizenode, align 1, !tbaa !7
%conv = zext i8 %1 to i32
%shl = shl i32 1, %conv
%sub = add nsw i32 %shl, -1
%and = and i32 %sub, %0
%node = getelementptr inbounds %struct.Table, %struct.Table* %t, i32 0, i32 7
%2 = load %struct.Node*, %struct.Node** %node, align 4, !tbaa !10
%3 = getelementptr inbounds %struct.Node, %struct.Node* %2, i32 %and, i32 1, i32 0, i32 1
%4 = load i32, i32* %3, align 4, !tbaa !11
%cmp = icmp eq i32 %4, 68
br i1 %cmp, label %land.lhs.true, label %if.end
land.lhs.true: ; preds = %entry
%value_ = getelementptr inbounds %struct.Node, %struct.Node* %2, i32 %and, i32 1, i32 0, i32 0
%5 = bitcast %union.Value* %value_ to %struct.TString**
%6 = load %struct.TString*, %struct.TString** %5, align 4, !tbaa !13
%cmp2 = icmp eq %struct.TString* %6, %key
br i1 %cmp2, label %if.then, label %if.end
if.then: ; preds = %land.lhs.true
%i_val = getelementptr inbounds %struct.Node, %struct.Node* %2, i32 %and, i32 0
br label %cleanup
if.end: ; preds = %land.lhs.true, %entry
%call = tail call %struct.TValue* @luaH_getstr(%struct.Table* %t, %struct.TString* %key) #2
br label %cleanup
cleanup: ; preds = %if.end, %if.then
%retval.0 = phi %struct.TValue* [ %i_val, %if.then ], [ %call, %if.end ]
ret %struct.TValue* %retval.0
}
declare %struct.TValue* @luaH_getstr(%struct.Table*, %struct.TString*) #1
attributes #0 = { nounwind "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-realign-stack" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-realign-stack" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #2 = { nounwind }
!llvm.ident = !{!0}
!0 = !{!"clang version 3.7.0 (trunk)"}
!1 = !{!2, !6, i64 8}
!2 = !{!"TString", !3, i64 0, !4, i64 4, !4, i64 5, !4, i64 6, !4, i64 7, !6, i64 8, !4, i64 16}
!3 = !{!"any pointer", !4, i64 0}
!4 = !{!"omnipotent char", !5, i64 0}
!5 = !{!"Simple C/C++ TBAA"}
!6 = !{!"int", !4, i64 0}
!7 = !{!8, !4, i64 7}
!8 = !{!"Table", !3, i64 0, !4, i64 4, !4, i64 5, !4, i64 6, !4, i64 7, !6, i64 8, !3, i64 12, !3, i64 16, !3, i64 20, !3, i64 24, !3, i64 28, !9, i64 32}
!9 = !{!"RaviArray", !3, i64 0, !4, i64 4, !6, i64 8, !6, i64 12}
!10 = !{!8, !3, i64 16}
!11 = !{!12, !6, i64 8}
!12 = !{!"TValue", !4, i64 0, !6, i64 8}
!13 = !{!3, !3, i64 0}

@ -1,4 +1,5 @@
setlocal
set PATH=%PATH%;"c:\Program Files (x86)\LLVM\bin"
clang -cc1 -O1 -disable-llvm-optzns -S -emit-llvm %1
rem clang -cc1 -O1 -disable-llvm-optzns -S -emit-llvm %1
clang -cc1 -O2 -S -emit-llvm %1

@ -299,10 +299,10 @@ OP_RAVI_LT_FF,/* A B C if ((RK(B) < RK(C)) ~= A) then pc++ */
OP_RAVI_LE_II,/* A B C if ((RK(B) <= RK(C)) ~= A) then pc++ */
OP_RAVI_LE_FF,/* A B C if ((RK(B) <= RK(C)) ~= A) then pc++ */
OP_RAVI_GETTABLEI,/* A B C R(A) := R(B)[RK(C)], integer key */
OP_RAVI_GETTABLES,/* A B C R(A) := R(B)[RK(C)], string key */
OP_RAVI_SETTABLEI,/* A B C R(A)[RK(B)] := RK(C), integer key */
OP_RAVI_SETTABLES,/* A B C R(A)[RK(B)] := RK(C), string key */
OP_RAVI_GETTABLE_I,/* A B C R(A) := R(B)[RK(C)], integer key */
OP_RAVI_GETTABLE_S,/* A B C R(A) := R(B)[RK(C)], string key */
OP_RAVI_SETTABLE_I,/* A B C R(A)[RK(B)] := RK(C), integer key */
OP_RAVI_SETTABLE_S,/* A B C R(A)[RK(B)] := RK(C), string key */
OP_RAVI_TOTAB, /* A R(A) := to_table(R(A)) */
OP_RAVI_MOVETAB, /* A B R(A) := R(B), check R(B) is a table */
OP_RAVI_SETUPVALT,/* A B UpValue[B] := to_table(R(A)) */

@ -69,9 +69,9 @@ extern "C" {
namespace ravi {
/*
** Lua typecode have certain bits that are used to
** Lua typecode have certain bits that are used to
** indicate variants or subtypes, or whether the type
** is collectible. The enumerated values below
** is collectible. The enumerated values below
** reflect the way these type codes get set within
** Lua values - these are the codes the JIT code must
** use.
@ -104,7 +104,7 @@ struct LuaLLVMTypes {
// Following are standard C types
// Must ensure that these types match
// between JIT and the C compiler
llvm::Type *C_voidT;
llvm::Type *C_voidT;
llvm::Type *C_doubleT;
llvm::Type *C_intptr_t;
llvm::Type *C_size_t;
@ -113,7 +113,7 @@ struct LuaLLVMTypes {
llvm::Type *C_int64_t;
llvm::Type *C_shortT;
llvm::Type *C_intT;
llvm::PointerType *C_pintT; /* pointer to int */
llvm::PointerType *C_pintT; /* pointer to int */
llvm::PointerType *C_pcharT; /* pointer to char */
llvm::Type *lua_NumberT;
@ -242,6 +242,9 @@ struct LuaLLVMTypes {
llvm::FunctionType *luaV_executeT;
llvm::FunctionType *luaV_gettableT;
llvm::FunctionType *luaV_settableT;
llvm::FunctionType *luaH_getintT;
llvm::FunctionType *luaH_setintT;
llvm::FunctionType *luaH_getstrT;
// Following are functions that handle specific bytecodes
// We cheat for these bytecodes by calling the function that
@ -318,6 +321,10 @@ struct LuaLLVMTypes {
llvm::MDNode *tbaa_RaviArray_typeT;
llvm::MDNode *tbaa_RaviArray_dataT;
llvm::MDNode *tbaa_RaviArray_lenT;
llvm::MDNode *tbaa_TString_hash;
llvm::MDNode *tbaa_Table_lsizenode;
llvm::MDNode *tbaa_Table_array;
llvm::MDNode *tbaa_Table_node;
};
class RAVI_API RaviJITStateImpl;
@ -532,6 +539,9 @@ struct RaviFunctionDef {
llvm::Function *luaV_divF;
llvm::Function *luaV_objlenF;
llvm::Function *luaC_upvalbarrierF;
llvm::Function *luaH_getstrF;
llvm::Function *luaH_getintF;
llvm::Function *luaH_setintF;
// Some cheats - these correspond to OPCODEs that
// are not inlined as of now
@ -612,7 +622,7 @@ public:
std::unique_ptr<RaviJITFunctionImpl>
create_function(llvm::IRBuilder<> &builder, RaviFunctionDef *def);
// Save proto->code[pc] into savedpc
// Save proto->code[pc] into savedpc
void emit_update_savedpc(RaviFunctionDef *def, int pc);
llvm::CallInst *CreateCall1(llvm::IRBuilder<> *builder, llvm::Value *func,
@ -681,6 +691,10 @@ public:
llvm::Value *emit_gep(RaviFunctionDef *def, const char *name, llvm::Value *s,
int arg1, int arg2, int arg3);
llvm::Value *emit_gep(RaviFunctionDef *def, const char *name,
llvm::Value *ptr, llvm::Value *arg1, int arg2, int arg3);
llvm::Value *emit_gep(RaviFunctionDef *def, const char *name,
llvm::Value *ptr, llvm::Value *arg1, int arg2);
// emit code for &ptr[offset]
llvm::Value *emit_array_get(RaviFunctionDef *def, llvm::Value *ptr,
@ -729,6 +743,22 @@ public:
// emit code to load the table value from register
llvm::Instruction *emit_load_reg_h(RaviFunctionDef *def, llvm::Value *ra);
// Gets the size of the hash table
llvm::Value *emit_table_get_hashsize(RaviFunctionDef *def, llvm::Value *table);
// Gets the location of the hash node for given key and table size
llvm::Value *emit_table_get_hashstr(RaviFunctionDef *def, llvm::Value *table, TString *key);
llvm::Value *emit_table_get_nodearray(RaviFunctionDef *def, llvm::Value *table);
llvm::Value *emit_table_get_keytype(RaviFunctionDef *def, llvm::Value *node, llvm::Value *index);
llvm::Value *emit_table_get_strkey(RaviFunctionDef *def, llvm::Value *node, llvm::Value *index);
llvm::Value *emit_table_get_value(RaviFunctionDef *def, llvm::Value *node, llvm::Value *index);
llvm::Instruction *emit_load_reg_s(RaviFunctionDef *def, llvm::Value *rb);
// emit code to load pointer to int array
llvm::Instruction *emit_load_reg_h_intarray(RaviFunctionDef *def,
llvm::Instruction *ra);
@ -932,6 +962,9 @@ public:
void emit_GETTABLE(RaviFunctionDef *def, int A, int B, int C, int pc);
void emit_GETTABLE_S(RaviFunctionDef *def, int A, int B, int C, int pc,
TString *key);
void emit_SELF(RaviFunctionDef *def, int A, int B, int C, int pc);
void emit_GETUPVAL(RaviFunctionDef *def, int A, int B, int pc);

@ -947,7 +947,6 @@ function test_idiv()
assert(s1 == s2)
print'test 41 (IDIV) OK'
end
assert(ravi.compile(test_idiv))
test_idiv()
@ -962,13 +961,17 @@ function test_tableaccess()
t.data.city = 'london'
return t.name, t.data.city
end
check(f, 'NEWTABLE', 'SETTABLES', 'NEWTABLE',
'SETTABLES', 'GETTABLES', 'SETTABLE',
'GETTABLES', 'GETTABLES' , 'GETTABLE',
check(f, 'NEWTABLE', 'SETTABLE_S', 'NEWTABLE',
'SETTABLE_S', 'GETTABLE_S', 'SETTABLE',
'GETTABLE_S', 'GETTABLE_S' , 'GETTABLE',
'RETURN', 'RETURN')
local a,b = f()
assert(a == 'dibyendu')
assert(b == 'london')
assert(ravi.compile(f))
a,b = f()
assert(a == 'dibyendu')
assert(b == 'london')
-- Test specialised version of GETTABLE and SETTABLE
-- when local variable is known to be of table type
@ -980,16 +983,19 @@ function test_tableaccess()
t[2][1] = 'london'
return t[1], t[2][1]
end
check(f, 'NEWTABLE', 'SETTABLEI', 'NEWTABLE',
'SETTABLEI', 'GETTABLEI', 'SETTABLE',
'GETTABLEI', 'GETTABLEI' , 'GETTABLE',
check(f, 'NEWTABLE', 'SETTABLE_I', 'NEWTABLE',
'SETTABLE_I', 'GETTABLE_I', 'SETTABLE',
'GETTABLE_I', 'GETTABLE_I' , 'GETTABLE',
'RETURN', 'RETURN')
local a,b = f()
assert(a == 'dibyendu')
assert(b == 'london')
assert(ravi.compile(f))
a,b = f()
assert(a == 'dibyendu')
assert(b == 'london')
print 'test tableaccess OK'
end
test_tableaccess()

@ -4,6 +4,11 @@
** See Copyright Notice in lua.h
*/
/*
** Portions Copyright (C) 2015 Dibyendu Majumdar
*/
#define lapi_c
#define LUA_CORE

@ -4,6 +4,10 @@
** See Copyright Notice in lua.h
*/
/*
** Portions Copyright (C) 2015 Dibyendu Majumdar
*/
#define lcode_c
#define LUA_CORE
@ -444,9 +448,9 @@ void luaK_dischargevars (FuncState *fs, expdesc *e) {
op = OP_RAVI_GETTABLE_AI;
else {
if (e->ravi_type == RAVI_TTABLE && e->u.ind.key_type == RAVI_TSTRING)
op = OP_RAVI_GETTABLES;
op = OP_RAVI_GETTABLE_S;
else if (e->ravi_type == RAVI_TTABLE && e->u.ind.key_type == RAVI_TNUMINT)
op = OP_RAVI_GETTABLEI;
op = OP_RAVI_GETTABLE_I;
else
op = OP_GETTABLE;
}
@ -762,10 +766,10 @@ void luaK_storevar (FuncState *fs, expdesc *var, expdesc *ex) {
op = OP_RAVI_SETTABLE_AII;
} else if (var->ravi_type == RAVI_TTABLE && var->u.ind.key_type == RAVI_TNUMINT) {
/* table with integer key */
op = OP_RAVI_SETTABLEI;
op = OP_RAVI_SETTABLE_I;
} else if (var->ravi_type == RAVI_TTABLE && var->u.ind.key_type == RAVI_TSTRING) {
/* table with string key */
op = OP_RAVI_SETTABLES;
op = OP_RAVI_SETTABLE_S;
}
}
int e = luaK_exp2RK(fs, ex);

@ -454,8 +454,8 @@ static const char *getobjname (Proto *p, int lastpc, int reg,
return getobjname(p, pc, b, name); /* get name for 'b' */
break;
}
case OP_RAVI_GETTABLEI:
case OP_RAVI_GETTABLES:
case OP_RAVI_GETTABLE_I:
case OP_RAVI_GETTABLE_S:
case OP_RAVI_GETTABLE_AI:
case OP_RAVI_GETTABLE_AF:
case OP_GETTABUP:

@ -4,6 +4,11 @@
** See Copyright Notice in lua.h
*/
/*
** Portions Copyright (C) 2015 Dibyendu Majumdar
*/
#define ldump_c
#define LUA_CORE

@ -4,6 +4,11 @@
** See Copyright Notice in lua.h
*/
/*
** Portions Copyright (C) 2015 Dibyendu Majumdar
*/
#define lfunc_c
#define LUA_CORE

@ -4,6 +4,11 @@
** See Copyright Notice in lua.h
*/
/*
** Portions Copyright (C) 2015 Dibyendu Majumdar
*/
#define lopcodes_c
#define LUA_CORE
@ -143,10 +148,10 @@ LUAI_DDEF const char *const luaP_opnames[NUM_OPCODES+1] = {
"LE_II", /* A B C if ((RK(B) <= RK(C)) ~= A) then pc++ */
"LE_FF", /* A B C if ((RK(B) <= RK(C)) ~= A) then pc++ */
"GETTABLEI", /* A B C R(A) := R(B)[RK(C)], integer key */
"GETTABLES", /* A B C R(A) := R(B)[RK(C)], string key */
"SETTABLEI", /* A B C R(A)[RK(B)] := RK(C), integer key */
"SETTABLES", /* A B C R(A)[RK(B)] := RK(C), string key */
"GETTABLE_I", /* A B C R(A) := R(B)[RK(C)], integer key */
"GETTABLE_S", /* A B C R(A) := R(B)[RK(C)], string key */
"SETTABLE_I", /* A B C R(A)[RK(B)] := RK(C), integer key */
"SETTABLE_S", /* A B C R(A)[RK(B)] := RK(C), string key */
"TOTAB", /* A R(A) := to_table(R(A)) */
"MOVETAB", /* A B R(A) := R(B), check R(B) is a table */
"SETUPVALT", /* A B UpValue[B] := to_table(R(A)) */
@ -274,10 +279,10 @@ LUAI_DDEF const lu_byte luaP_opmodes[NUM_OPCODES] = {
,opmode(1, 0, OpArgK, OpArgK, iABC) /* OP_RAVI_LE_II */
,opmode(1, 0, OpArgK, OpArgK, iABC) /* OP_RAVI_LE_FF */
,opmode(0, 1, OpArgR, OpArgK, iABC) /* OP_RAVI_GETTABLEI */
,opmode(0, 1, OpArgR, OpArgK, iABC) /* OP_RAVI_GETTABLES */
,opmode(0, 0, OpArgK, OpArgK, iABC) /* OP_RAVI_SETTABLEI */
,opmode(0, 0, OpArgK, OpArgK, iABC) /* OP_RAVI_SETTABLES */
,opmode(0, 1, OpArgR, OpArgK, iABC) /* OP_RAVI_GETTABLE_I */
,opmode(0, 1, OpArgR, OpArgK, iABC) /* OP_RAVI_GETTABLE_S */
,opmode(0, 0, OpArgK, OpArgK, iABC) /* OP_RAVI_SETTABLE_I */
,opmode(0, 0, OpArgK, OpArgK, iABC) /* OP_RAVI_SETTABLE_S */
,opmode(0, 1, OpArgN, OpArgN, iABC) /* OP_RAVI_TOTAB A R(A) := check_table(R(A)) */
,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_RAVI_MOVETAB A B R(A) := R(B), check R(B) is a table */
@ -430,6 +435,7 @@ static void PrintCode(const Proto* f)
case OP_RAVI_SETUPVALF:
case OP_RAVI_SETUPVALAI:
case OP_RAVI_SETUPVALAF:
case OP_RAVI_SETUPVALT:
case OP_SETUPVAL:
printf("\t; %s",UPVALNAME(b));
break;
@ -443,12 +449,16 @@ static void PrintCode(const Proto* f)
if (ISK(c)) { printf(" "); PrintConstant(f,INDEXK(c)); }
break;
case OP_GETTABLE:
case OP_RAVI_GETTABLE_I:
case OP_RAVI_GETTABLE_S:
case OP_RAVI_GETTABLE_AF:
case OP_RAVI_GETTABLE_AI:
case OP_SELF:
if (ISK(c)) { printf("\t; "); PrintConstant(f,INDEXK(c)); }
break;
case OP_SETTABLE:
case OP_RAVI_SETTABLE_I:
case OP_RAVI_SETTABLE_S:
case OP_RAVI_SETTABLE_AF:
case OP_RAVI_SETTABLE_AFF:
case OP_RAVI_SETTABLE_AI:

@ -4,6 +4,10 @@
** See Copyright Notice in lua.h
*/
/*
** Portions Copyright (C) 2015 Dibyendu Majumdar
*/
#define lparser_c
#define LUA_CORE
@ -304,16 +308,16 @@ static TString *str_checkname (LexState *ls) {
* expression kind in e->k, e->u.info may have a register
* or bytecode
*/
static void init_exp (expdesc *e, expkind k, int i, ravitype_t tt) {
static void init_exp (expdesc *e, expkind k, int info, ravitype_t tt) {
e->f = e->t = NO_JUMP;
e->k = k;
e->u.info = i;
e->u.info = info;
/* RAVI change; added type */
e->ravi_type = tt;
}
/* create a string constant expression, constant's location stored in
* e->u.info, e->ravi_type = LUA_STRING
* e->u.info, e->ravi_type = RAVI_TSTRING
*/
static void codestring (LexState *ls, expdesc *e, TString *s) {
init_exp(e, VK, luaK_stringK(ls->fs, s), RAVI_TSTRING);

@ -4,6 +4,10 @@
** See Copyright Notice in lua.h
*/
/*
** Portions Copyright (C) 2015 Dibyendu Majumdar
*/
#define lundump_c
#define LUA_CORE

@ -4,6 +4,11 @@
** See Copyright Notice in lua.h
*/
/*
** Portions Copyright (C) 2015 Dibyendu Majumdar
*/
#define lvm_c
#define LUA_CORE
@ -871,7 +876,7 @@ newframe: /* reentry point when frame changes (call/return) */
int b = GETARG_B(i);
Protect(luaV_gettable(L, cl->upvals[b]->v, RKC(i), ra));
} break;
case OP_RAVI_GETTABLEI: {
case OP_RAVI_GETTABLE_I: {
TValue *rb = RB(i);
TValue *rc = RKC(i);
lua_Integer idx = ivalue(rc);
@ -880,7 +885,7 @@ newframe: /* reentry point when frame changes (call/return) */
setobj2s(L, ra, v);
break;
}
case OP_RAVI_GETTABLES: {
case OP_RAVI_GETTABLE_S: {
if (ISK(GETARG_C(i))) {
TValue *kv = k + INDEXK(GETARG_C(i));
TString *key = tsvalue(kv);
@ -924,8 +929,8 @@ newframe: /* reentry point when frame changes (call/return) */
setobj(L, uv->v, ra);
luaC_upvalbarrier(L, uv);
} break;
case OP_RAVI_SETTABLEI:
case OP_RAVI_SETTABLES:
case OP_RAVI_SETTABLE_I:
case OP_RAVI_SETTABLE_S:
case OP_SETTABLE: {
Protect(luaV_settable(L, ra, RKB(i), RKC(i)));
} break;

@ -114,6 +114,25 @@ llvm::Value *RaviCodeGenerator::emit_gep(RaviFunctionDef *def, const char *name,
return def->builder->CreateInBoundsGEP(s, values, name);
}
llvm::Value *RaviCodeGenerator::emit_gep(RaviFunctionDef *def, const char *name,
llvm::Value *ptr, llvm::Value *offset,
int arg1, int arg2) {
llvm::SmallVector<llvm::Value *, 3> values;
values.push_back(offset);
values.push_back(def->types->kInt[arg1]);
values.push_back(def->types->kInt[arg2]);
return def->builder->CreateInBoundsGEP(ptr, values, name);
}
llvm::Value *RaviCodeGenerator::emit_gep(RaviFunctionDef *def, const char *name,
llvm::Value *ptr, llvm::Value *offset,
int arg1) {
llvm::SmallVector<llvm::Value *, 2> values;
values.push_back(offset);
values.push_back(def->types->kInt[arg1]);
return def->builder->CreateInBoundsGEP(ptr, values, name);
}
llvm::Value *
RaviCodeGenerator::emit_gep_ci_func_value_gc_asLClosure(RaviFunctionDef *def) {
// emit code for (LClosure *)ci->func->value_.gc
@ -147,21 +166,23 @@ RaviCodeGenerator::emit_load_proto_sizep(RaviFunctionDef *def) {
// enables this to be updated per bytecode instruction - this is only
// required if someone wishes to set a line hook. The second option
// is very expensive and will inhibit optimizations, hence it is optional
// See issue #15
// See issue #15
void RaviCodeGenerator::emit_update_savedpc(RaviFunctionDef *def, int pc) {
// Get proto->code
llvm::Value *proto_code_ptr = emit_gep(def, "code", def->proto_ptr, 0, 15);
llvm::Instruction *code_ptr = def->builder->CreateLoad(proto_code_ptr);
code_ptr->setMetadata(llvm::LLVMContext::MD_tbaa, def->types->tbaa_Proto_codeT);
code_ptr->setMetadata(llvm::LLVMContext::MD_tbaa,
def->types->tbaa_Proto_codeT);
// Need to set savedpc to the next instruction rather than current as that is
// what the VM does in interpreted mode
llvm::Value *code_offset_ptr = emit_array_get(def, code_ptr, pc+1);
llvm::Value *code_offset_ptr = emit_array_get(def, code_ptr, pc + 1);
llvm::Value *savedpc_ptr = emit_gep(def, "savedpc", def->ci_val, 0, 4, 1);
llvm::Instruction *ins = def->builder->CreateStore(code_offset_ptr, savedpc_ptr);
ins->setMetadata(llvm::LLVMContext::MD_tbaa, def->types->tbaa_CallInfo_savedpcT);
llvm::Instruction *ins =
def->builder->CreateStore(code_offset_ptr, savedpc_ptr);
ins->setMetadata(llvm::LLVMContext::MD_tbaa,
def->types->tbaa_CallInfo_savedpcT);
}
llvm::Value *RaviCodeGenerator::emit_array_get(RaviFunctionDef *def,
llvm::Value *ptr, int offset) {
// emit code for &ptr[offset]
@ -267,6 +288,77 @@ llvm::Instruction *RaviCodeGenerator::emit_load_reg_h(RaviFunctionDef *def,
return h;
}
llvm::Value *RaviCodeGenerator::emit_table_get_hashsize(RaviFunctionDef *def,
llvm::Value *t) {
// Obtain the lsizenode of the hash table
llvm::Value *lsizenode_ptr = emit_gep(def, "lsizenode", t, 0, 4);
llvm::Instruction *lsizenode = def->builder->CreateLoad(lsizenode_ptr);
lsizenode->setMetadata(llvm::LLVMContext::MD_tbaa,
def->types->tbaa_Table_lsizenode);
// convert to integer (lsizenode is a byte)
llvm::Value *intsize =
def->builder->CreateZExt(lsizenode, def->types->C_intT);
// #define twoto(x) (1<<(x))
// #define sizenode(t) (twoto((t)->lsizenode))
llvm::Value *size = def->builder->CreateShl(def->types->kInt[1], intsize);
return size;
}
llvm::Value *RaviCodeGenerator::emit_table_get_hashstr(RaviFunctionDef *def,
llvm::Value *table,
TString *key) {
llvm::Value *size = emit_table_get_hashsize(def, table);
unsigned int hash = key->hash;
// #define lmod(s,size) (cast(int, (s) & ((size)-1)))
llvm::Value *sizeminusone =
def->builder->CreateNSWSub(size, def->types->kInt[1]);
llvm::Value *offset = def->builder->CreateAnd(
llvm::ConstantInt::get(def->types->C_intT, hash), sizeminusone);
return offset;
}
llvm::Value *RaviCodeGenerator::emit_table_get_nodearray(RaviFunctionDef *def,
llvm::Value *table) {
// Get access to the node array
llvm::Value *node_ptr = emit_gep(def, "node", table, 0, 7);
llvm::Instruction *node = def->builder->CreateLoad(node_ptr);
node->setMetadata(llvm::LLVMContext::MD_tbaa, def->types->tbaa_Table_node);
return node;
}
llvm::Value *RaviCodeGenerator::emit_table_get_keytype(RaviFunctionDef *def,
llvm::Value *node,
llvm::Value *index) {
llvm::Value *ktype_ptr = emit_gep(def, "keytype", node, index, 1, 1);
llvm::Instruction *ktype = def->builder->CreateLoad(ktype_ptr);
ktype->setMetadata(llvm::LLVMContext::MD_tbaa, def->types->tbaa_TValue_ttT);
return ktype;
}
llvm::Value *RaviCodeGenerator::emit_table_get_strkey(RaviFunctionDef *def,
llvm::Value *node,
llvm::Value *index) {
llvm::Value *value_ptr = emit_gep(def, "keyvalue", node, index, 1, 0);
llvm::Value *sptr =
def->builder->CreateBitCast(value_ptr, def->types->ppTStringT);
llvm::Instruction *keyvalue = def->builder->CreateLoad(sptr);
keyvalue->setMetadata(llvm::LLVMContext::MD_tbaa, def->types->tbaa_ppointerT);
return keyvalue;
}
llvm::Value *RaviCodeGenerator::emit_table_get_value(RaviFunctionDef *def, llvm::Value *node, llvm::Value *index) {
return emit_gep(def, "nodeval", node, index, 0);
}
llvm::Instruction *RaviCodeGenerator::emit_load_reg_s(RaviFunctionDef *def,
llvm::Value *rb) {
llvm::Value *rb_s = def->builder->CreateBitCast(rb, def->types->ppTStringT);
llvm::Instruction *s = def->builder->CreateLoad(rb_s);
// Following TBAA is okay as the field type is a pointer
s->setMetadata(llvm::LLVMContext::MD_tbaa, def->types->tbaa_TValue_hT);
return s;
}
llvm::Instruction *
RaviCodeGenerator::emit_load_reg_h_floatarray(RaviFunctionDef *def,
llvm::Instruction *h) {
@ -758,10 +850,10 @@ bool RaviCodeGenerator::canCompile(Proto *p) {
case OP_RAVI_SHR_II:
case OP_SHR:
case OP_SHL:
case OP_RAVI_GETTABLEI:
case OP_RAVI_GETTABLES:
case OP_RAVI_SETTABLEI:
case OP_RAVI_SETTABLES:
case OP_RAVI_GETTABLE_I:
case OP_RAVI_GETTABLE_S:
case OP_RAVI_SETTABLE_I:
case OP_RAVI_SETTABLE_S:
break;
default:
return false;
@ -914,6 +1006,16 @@ void RaviCodeGenerator::emit_extern_declarations(RaviFunctionDef *def) {
def->luaV_gettableF = def->raviF->addExternFunction(
def->types->luaV_gettableT, reinterpret_cast<void *>(&luaV_gettable),
"luaV_gettable");
def->luaH_getintF = def->raviF->addExternFunction(
def->types->luaH_getintT, reinterpret_cast<void *>(&luaH_getint),
"luaH_getint");
def->luaH_setintF = def->raviF->addExternFunction(
def->types->luaH_setintT, reinterpret_cast<void *>(&luaH_setint),
"luaH_setint");
def->luaH_getstrF = def->raviF->addExternFunction(
def->types->luaH_getstrT, reinterpret_cast<void *>(&luaH_getstr),
"luaH_getstr");
def->raviV_op_loadnilF = def->raviF->addExternFunction(
def->types->raviV_op_loadnilT,
reinterpret_cast<void *>(&raviV_op_loadnil), "raviV_op_loadnil");
@ -1101,6 +1203,8 @@ RaviCodeGenerator::emit_gep_upval_value(RaviFunctionDef *def,
void RaviCodeGenerator::compile(lua_State *L, Proto *p,
ravi_compile_options_t *options) {
TValue *k = p->k;
bool doDump = options ? options->dump_level != 0 : 0;
bool doVerify = options ? options->verification_level != 0 : 0;
bool omitArrayGetRangeCheck =
@ -1409,15 +1513,29 @@ void RaviCodeGenerator::compile(lua_State *L, Proto *p,
emit_CALL(def, A, B, C, pc);
} break;
case OP_RAVI_SETTABLEI:
case OP_RAVI_SETTABLES:
case OP_RAVI_SETTABLE_I:
case OP_RAVI_SETTABLE_S:
case OP_SETTABLE: {
int B = GETARG_B(i);
int C = GETARG_C(i);
emit_SETTABLE(def, A, B, C, pc);
} break;
case OP_RAVI_GETTABLEI:
case OP_RAVI_GETTABLES:
case OP_RAVI_GETTABLE_S: {
int C = GETARG_C(i);
int B = GETARG_B(i);
if (ISK(C)) {
TValue *kv = k + INDEXK(C);
TString *key = tsvalue(kv);
if (key->tt == LUA_TSHRSTR) {
emit_GETTABLE_S(def, A, B, C, pc, key);
} else {
emit_GETTABLE(def, A, B, C, pc);
}
} else {
emit_GETTABLE(def, A, B, C, pc);
}
} break;
case OP_RAVI_GETTABLE_I:
case OP_GETTABLE: {
int B = GETARG_B(i);
int C = GETARG_C(i);

@ -32,7 +32,8 @@ void RaviCodeGenerator::emit_SELF(RaviFunctionDef *def, int A, int B, int C,
// Protect(luaV_gettable(L, rb, RKC(i), ra));
bool traced = emit_debug_trace(def, OP_SELF, pc);
// Below may invoke metamethod so we set savedpc
if (!traced) emit_update_savedpc(def, pc);
if (!traced)
emit_update_savedpc(def, pc);
emit_load_base(def);
llvm::Value *rb = emit_gep_register(def, B);
llvm::Value *ra1 = emit_gep_register(def, A + 1);
@ -47,7 +48,8 @@ void RaviCodeGenerator::emit_LEN(RaviFunctionDef *def, int A, int B, int pc) {
// Protect(luaV_objlen(L, ra, RB(i)));
bool traced = emit_debug_trace(def, OP_LEN, pc);
// Below may invoke metamethod so we set savedpc
if (!traced) emit_update_savedpc(def, pc);
if (!traced)
emit_update_savedpc(def, pc);
emit_load_base(def);
llvm::Value *ra = emit_gep_register(def, A);
llvm::Value *rb = emit_gep_register(def, B);
@ -60,7 +62,8 @@ void RaviCodeGenerator::emit_SETTABLE(RaviFunctionDef *def, int A, int B, int C,
// Protect(luaV_settable(L, ra, RKB(i), RKC(i)));
bool traced = emit_debug_trace(def, OP_SETTABLE, pc);
// Below may invoke metamethod so we set savedpc
if (!traced) emit_update_savedpc(def, pc);
if (!traced)
emit_update_savedpc(def, pc);
emit_load_base(def);
llvm::Value *ra = emit_gep_register(def, A);
llvm::Value *rb = emit_gep_register_or_constant(def, B);
@ -74,7 +77,8 @@ void RaviCodeGenerator::emit_GETTABLE(RaviFunctionDef *def, int A, int B, int C,
// Protect(luaV_gettable(L, RB(i), RKC(i), ra));
bool traced = emit_debug_trace(def, OP_GETTABLE, pc);
// Below may invoke metamethod so we set savedpc
if (!traced) emit_update_savedpc(def, pc);
if (!traced)
emit_update_savedpc(def, pc);
emit_load_base(def);
llvm::Value *ra = emit_gep_register(def, A);
llvm::Value *rb = emit_gep_register(def, B);
@ -82,6 +86,126 @@ void RaviCodeGenerator::emit_GETTABLE(RaviFunctionDef *def, int A, int B, int C,
CreateCall4(def->builder, def->luaV_gettableF, def->L, rb, rc, ra);
}
// Emit inline code for accessing a table element using a string key
// We try to access the element using the hash part but if the
// key is not in the main position then we fall back on luaH_getstr().
// IMPORTANT - this emitter should only be called when key is known to
// to be short string
void RaviCodeGenerator::emit_GETTABLE_S(RaviFunctionDef *def, int A, int B,
int C, int pc, TString *key) {
// The code we want to generate is this:
// struct Node *n = hashstr(t, key);
// const struct TValue *k = gkey(n);
// if (ttisshrstring(k) && eqshrstr(tsvalue(k), key))
// return gval(n);
// return luaH_getstr(t, key);
// A number of macros are involved above do the
// the generated code is somewhat more complex
// we don't need to refresh base here as the lua_State is not being touched
emit_debug_trace(def, OP_RAVI_GETTABLE_S, pc);
llvm::Value *ra = emit_gep_register(def, A);
llvm::Value *rb = emit_gep_register(def, B);
// Fortunately as we are dealing with a string constant we already
// know the hash code of the key
//unsigned int hash = key->hash;
// Get the hash table
llvm::Instruction *t = emit_load_reg_h(def, rb);
// Obtain the lsizenode of the hash table
//llvm::Value *lsizenode_ptr = emit_gep(def, "lsizenode", t, 0, 4);
//llvm::Instruction *lsizenode = def->builder->CreateLoad(lsizenode_ptr);
//lsizenode->setMetadata(llvm::LLVMContext::MD_tbaa,
// def->types->tbaa_Table_lsizenode);
//// convert to integer (lsizenode is a byte)
//llvm::Value *intsize =
// def->builder->CreateZExt(lsizenode, def->types->C_intT);
//// #define twoto(x) (1<<(x))
//// #define sizenode(t) (twoto((t)->lsizenode))
//llvm::Value *size = def->builder->CreateShl(def->types->kInt[1], intsize);
// #define lmod(s,size) (cast(int, (s) & ((size)-1)))
//llvm::Value *sizeminusone =
// def->builder->CreateNSWSub(size, def->types->kInt[1]);
//llvm::Value *offset = def->builder->CreateAnd(
// llvm::ConstantInt::get(def->types->C_intT, hash), sizeminusone);
llvm::Value *offset = emit_table_get_hashstr(def, t, key);
// Get access to the node array
//llvm::Value *node_ptr = emit_gep(def, "node", t, 0, 7);
//llvm::Instruction *node = def->builder->CreateLoad(node_ptr);
//node->setMetadata(llvm::LLVMContext::MD_tbaa, def->types->tbaa_Table_node);
llvm::Value *node = emit_table_get_nodearray(def, t);
// Now we need to get to the right element in the node array
// and retrieve the type information which is held there
//llvm::Value *ktype_ptr = emit_gep(def, "keytype", node, offset, 1, 1);
//llvm::Instruction *ktype = def->builder->CreateLoad(ktype_ptr);
//ktype->setMetadata(llvm::LLVMContext::MD_tbaa, def->types->tbaa_TValue_ttT);
llvm::Value *ktype = emit_table_get_keytype(def, node, offset);
// We need to check that the key type is also short string
llvm::Value *is_shortstring =
emit_is_value_of_type(def, ktype, LUA__TSHRSTR, "is_shortstring");
llvm::BasicBlock *testkey =
llvm::BasicBlock::Create(def->jitState->context(), "testkey");
llvm::BasicBlock *testok =
llvm::BasicBlock::Create(def->jitState->context(), "testok");
llvm::BasicBlock *testfail =
llvm::BasicBlock::Create(def->jitState->context(), "testfail");
llvm::BasicBlock *testend =
llvm::BasicBlock::Create(def->jitState->context(), "testend");
def->builder->CreateCondBr(is_shortstring, testkey, testfail);
// Now we need to compare the keys
def->f->getBasicBlockList().push_back(testkey);
def->builder->SetInsertPoint(testkey);
// Get the key from the node
//llvm::Value *value_ptr = emit_gep(def, "keyvalue", node, offset, 1, 0);
//llvm::Value *sptr =
// def->builder->CreateBitCast(value_ptr, def->types->ppTStringT);
//llvm::Instruction *keyvalue = def->builder->CreateLoad(sptr);
//keyvalue->setMetadata(llvm::LLVMContext::MD_tbaa, def->types->tbaa_ppointerT);
llvm::Value *keyvalue = emit_table_get_strkey(def, node, offset);
// Cast the pointer to a intptr so we can compare
llvm::Value *intptr =
def->builder->CreatePtrToInt(keyvalue, def->types->C_intptr_t);
llvm::Value *ourptr =
llvm::ConstantInt::get(def->types->C_intptr_t, (intptr_t)key);
// Compare the two pointers
// If they match then we found the element
llvm::Value *same = def->builder->CreateICmpEQ(intptr, ourptr);
def->builder->CreateCondBr(same, testok, testfail);
// If key found return the value
def->f->getBasicBlockList().push_back(testok);
def->builder->SetInsertPoint(testok);
//llvm::Value *value1 = emit_gep(def, "nodeval", node, offset, 0);
llvm::Value *value1 = emit_table_get_value(def, node, offset);
def->builder->CreateBr(testend);
// Not found so call luaH_getstr
def->f->getBasicBlockList().push_back(testfail);
def->builder->SetInsertPoint(testfail);
llvm::Value *rc = emit_gep_register_or_constant(def, C);
llvm::Value *value2 =
CreateCall2(def->builder, def->luaH_getstrF, t, emit_load_reg_s(def, rc));
def->builder->CreateBr(testend);
// merge
def->f->getBasicBlockList().push_back(testend);
def->builder->SetInsertPoint(testend);
llvm::PHINode *phi = def->builder->CreatePHI(def->types->pTValueT, 2);
phi->addIncoming(value1, testok);
phi->addIncoming(value2, testfail);
emit_assign(def, ra, phi);
}
void RaviCodeGenerator::emit_GETTABLE_AF(RaviFunctionDef *def, int A, int B,
int C, bool omitArrayGetRangeCheck,
int pc) {
@ -398,7 +522,8 @@ void RaviCodeGenerator::emit_GETTABUP(RaviFunctionDef *def, int A, int B, int C,
// Protect(luaV_gettable(L, cl->upvals[b]->v, RKC(i), ra));
bool traced = emit_debug_trace(def, OP_GETTABUP, pc);
// Below may invoke metamethod so we set savedpc
if (!traced) emit_update_savedpc(def, pc);
if (!traced)
emit_update_savedpc(def, pc);
emit_load_base(def);
llvm::Value *ra = emit_gep_register(def, A);
llvm::Value *rc = emit_gep_register_or_constant(def, C);
@ -417,7 +542,8 @@ void RaviCodeGenerator::emit_SETTABUP(RaviFunctionDef *def, int A, int B, int C,
// Protect(luaV_settable(L, cl->upvals[a]->v, RKB(i), RKC(i)));
bool traced = emit_debug_trace(def, OP_SETTABUP, pc);
if (!traced) emit_update_savedpc(def, pc);
if (!traced)
emit_update_savedpc(def, pc);
emit_load_base(def);
llvm::Value *rb = emit_gep_register_or_constant(def, B);
llvm::Value *rc = emit_gep_register_or_constant(def, C);

@ -866,6 +866,29 @@ LuaLLVMTypes::LuaLLVMTypes(llvm::LLVMContext &context) : mdbuilder(context) {
raviV_op_shrT =
llvm::FunctionType::get(llvm::Type::getVoidTy(context), elements, false);
// const TValue *luaH_getint(Table *t, lua_Integer key);
elements.clear();
elements.push_back(pTableT);
elements.push_back(lua_IntegerT);
luaH_getintT =
llvm::FunctionType::get(pTValueT, elements, false);
// void luaH_setint(lua_State *L, Table *t, lua_Integer key, TValue *value);
elements.clear();
elements.push_back(plua_StateT);
elements.push_back(pTableT);
elements.push_back(lua_IntegerT);
elements.push_back(pTValueT);
luaH_setintT =
llvm::FunctionType::get(llvm::Type::getVoidTy(context), elements, false);
// const TValue *luaH_getstr(Table *t, TString *key);
elements.clear();
elements.push_back(pTableT);
elements.push_back(pTStringT);
luaH_getstrT =
llvm::FunctionType::get(pTValueT, elements, false);
// void raviH_set_int(lua_State *L, Table *t, lua_Unsigned key, lua_Integer
// value);
elements.clear();
@ -1161,20 +1184,20 @@ LuaLLVMTypes::LuaLLVMTypes(llvm::LLVMContext &context) : mdbuilder(context) {
std::pair<llvm::MDNode *, uint64_t>(tbaa_charT, 13)); /* modifiers */
tbaa_RaviArrayT = mdbuilder.createTBAAStructTypeNode("RaviArray", nodes);
// Table
// Table TBAA struct type
nodes.clear();
nodes.push_back(std::pair<llvm::MDNode *, uint64_t>(tbaa_pointerT, 0));
nodes.push_back(std::pair<llvm::MDNode *, uint64_t>(tbaa_charT, 4));
nodes.push_back(std::pair<llvm::MDNode *, uint64_t>(tbaa_charT, 5));
nodes.push_back(std::pair<llvm::MDNode *, uint64_t>(tbaa_charT, 6));
nodes.push_back(std::pair<llvm::MDNode *, uint64_t>(tbaa_charT, 7));
nodes.push_back(std::pair<llvm::MDNode *, uint64_t>(tbaa_intT, 8));
nodes.push_back(std::pair<llvm::MDNode *, uint64_t>(tbaa_pointerT, 12));
nodes.push_back(std::pair<llvm::MDNode *, uint64_t>(tbaa_pointerT, 16));
nodes.push_back(std::pair<llvm::MDNode *, uint64_t>(tbaa_pointerT, 20));
nodes.push_back(std::pair<llvm::MDNode *, uint64_t>(tbaa_pointerT, 24));
nodes.push_back(std::pair<llvm::MDNode *, uint64_t>(tbaa_pointerT, 28));
nodes.push_back(std::pair<llvm::MDNode *, uint64_t>(tbaa_RaviArrayT, 32));
nodes.push_back(std::pair<llvm::MDNode *, uint64_t>(tbaa_pointerT, 0)); /* next */
nodes.push_back(std::pair<llvm::MDNode *, uint64_t>(tbaa_charT, 4)); /* tt */
nodes.push_back(std::pair<llvm::MDNode *, uint64_t>(tbaa_charT, 5)); /* marked */
nodes.push_back(std::pair<llvm::MDNode *, uint64_t>(tbaa_charT, 6)); /* flags */
nodes.push_back(std::pair<llvm::MDNode *, uint64_t>(tbaa_charT, 7)); /* lsizenode */
nodes.push_back(std::pair<llvm::MDNode *, uint64_t>(tbaa_intT, 8)); /* size array */
nodes.push_back(std::pair<llvm::MDNode *, uint64_t>(tbaa_pointerT, 12)); /* array */
nodes.push_back(std::pair<llvm::MDNode *, uint64_t>(tbaa_pointerT, 16)); /* node */
nodes.push_back(std::pair<llvm::MDNode *, uint64_t>(tbaa_pointerT, 20)); /* lastfree */
nodes.push_back(std::pair<llvm::MDNode *, uint64_t>(tbaa_pointerT, 24)); /* metatable */
nodes.push_back(std::pair<llvm::MDNode *, uint64_t>(tbaa_pointerT, 28)); /* gclist */
nodes.push_back(std::pair<llvm::MDNode *, uint64_t>(tbaa_RaviArrayT, 32)); /* ravi_array */
tbaa_TableT = mdbuilder.createTBAAStructTypeNode("Table", nodes);
tbaa_RaviArray_dataT =
@ -1183,6 +1206,9 @@ LuaLLVMTypes::LuaLLVMTypes(llvm::LLVMContext &context) : mdbuilder(context) {
mdbuilder.createTBAAStructTagNode(tbaa_TableT, tbaa_intT, 36);
tbaa_RaviArray_typeT =
mdbuilder.createTBAAStructTagNode(tbaa_TableT, tbaa_charT, 44);
tbaa_Table_lsizenode = mdbuilder.createTBAAStructTagNode(tbaa_TableT, tbaa_charT, 7);
tbaa_Table_array = mdbuilder.createTBAAStructTagNode(tbaa_TableT, tbaa_pointerT, 12);
tbaa_Table_node = mdbuilder.createTBAAStructTagNode(tbaa_TableT, tbaa_pointerT, 16);
}
void LuaLLVMTypes::dump() {

Loading…
Cancel
Save