diff --git a/CMakeLists.txt b/CMakeLists.txt index 98dfac4..8757722 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,7 +89,9 @@ add_library(stapi src/ParsedBlock.cpp src/ControlGraph.cpp + src/TauLinker.cpp src/ControlGraphVisualizer.cpp + src/TypeAnalyzer.cpp ) set(MM_CPP_FILES diff --git a/doc/types.txt b/doc/types.txt index 176c12d..976fac9 100644 --- a/doc/types.txt +++ b/doc/types.txt @@ -17,7 +17,7 @@ id: param ^param -Ти́повое выражение: +Т́иповое выражение: self::*, param::* -> self, param @@ -26,13 +26,13 @@ ^ a + b Контекст object sum: intvara and: intvarb - self::*, (SmallInt), (SmallInt) -> self, (SmallInt) + self::*, a::(SmallInt), b::(SmallInt) -> self', (SmallInt) Контекст object sum: 2 and: 3 self::*, a::2, b::3 -> self, 5 Обобщенный метод - self::*, a::*, b::* -> self'::*, * + self::*, a::*, b::* -> self', * то есть, литеральные значения протаскиваются прямо через тип @@ -72,9 +72,10 @@ Контекст object dirty: 42 self::[_,_,_], param::42 -> self'::[_,_,42], 43 +Примечание: в случае утекания self, мы не можем утверждать, что self' будет таким. Придется фолбечиться на self'::* -А теперь самый вынос мозга — система контекста виртуальной машине — это монод в категории эндофункторов! Монада то бишь. +А теперь самый вынос мозга — система контекста виртуальной машине — это моноид в категории эндофункторов! Монада то бишь. Переход виртуальной машины от одного состояния к другому — это монадическая операция. А это внезапно означает: @@ -106,6 +107,7 @@ Монадические отношения в связи с объектом self позволяют формализовать тот факт, что при вызове метода поля самого объекта не меняются. Это позволяет более смело выполнять оптимизации и связывать далекие участки графа без опасения что типы совпали «случайно». +Поправка — нихрена :( Если self утек в другой объект, то вызывая методы он может поменять и нас. Только при инлайнинге и доказательсве. Типы методов: @@ -184,3 +186,90 @@ Впрочем, ветви кэша кодируются как вызовы специализированных версий методов, поскольку в пределах ветви класс отправителя (self) становится известен. + +Блоки, акт второй. + +Классификация блоков относительно типов + ContextFree [ 42 ], [ :x | x + 1 ], [ :x :y | x < y ] + ContextAccessor [ x message ], [ x + 1 ], [ anArgument message: aTemporary ] + ContextMutator [ x <- nil ], [ :x | sum <- sum + x ] + + BlockReturn [ ^42 ] + +Классификация точки вызова блоков + Never + Once y <- [ :x | x ] value: 42 + Maybe + Multi 1 to: 10 do: [ :x | sum <- sum + x ] + Escape *::anObject take: (Block)::aBlock + + +В зависимости от вида блока и точки его вызова, вывод типов может быть реализован по-разному. + +В наиболее общем случае необходимо выполнять подстановку графа блока в лексический контекст +вызывающего метода в зависимости от того как происходит работа с блоком в теле обработчика сообщения. + +Never + Блок не учитывается в выводе типов + +Once + Блок подключается параллельно инструкции, принимающей его параметром, например 1 to: 10: do: [ ... ] + +Maybe + То же, что и для Once, но есть параллельное ребро мимо блока + +Multi + То же, что и Multi, но еще добавляется возвратное ребро в начало блока + +Escape + Вызовы этого типа не могут быть представлены в виде графа и блокируют вывод типов для задействованных переменных + +После построения inline графа, выполняется проход TauLinker-а на нем, +полученные тау узлы могут быть использованы для вывода типов переменных. + + +Итак, алгоритм. + +Для метода, в лексическом контексте которого объявляется блок: + 1. Получаем список переменных, которые мутирует блок (известно после анализа графа блока) + 2. Выполняем анализ для каждой посылки, которая использует блок как параметр + 3. В точку вызова передаем тип всех временных переменных, используемых блоком (как аргументы) + 4. После анализа посылки изучаем контекст на предмет поменявшихся типов переменных + 5. Если тип поменялся, то маркируем текущую посылку как мутатор + 6. После маркировки всех мутаторов обновляем тау узлы графа и точки их использования + 7. Пересчитываем все заново с обновленным графом (новых мутаторов добавиться не должно) + +Для метода, принимающего литеральный блок параметром: + 1. В каждой посылке, использующей переданный аргументом блок выполняем анализ «как есть» + 2. Аккумулируем типы меняющихся переменных + 3. Возвращаем аккумулированный результат + +Для методов Block>>value* (код обработчика примитива 8) + 1. Проводим анализ блока, передав типы аргументов и переменных + 2. Получаем типы переменных и результата + 3. Возвращаем все добро наверх + + +Проходы анализатора, в зависимости от типа метода: + 1. Метод, не содержащий ветвлений доказывается за 1 проход + 2. Метод, с литеральными условиями при ветвлениях доказывается за 1 проход + 3. Метод, не содержащий циклов (обратных ребер) доказывается за 1 проход + 4. Метод, содержащий циклы, но не имеющий тау узлов с обратными ребрами, доказывается за 1? проход + 5. Метод, содержащий циклы и имеющий циклическую зависимость по данным (x <- x + 1), доказывается за 2 прохода + + 6. Метод, содержащий литеральные блоки, вне циклов: если нет замыканий или только читают — 1 проход, если пишут — минимум 2 прохода + 7. Метод, содержащий литеральные блоки, в циклах — минимум 3 прохода + 8. Метод, содержащий литеральные блоки, записанные в переменную + + Блоки, вызываемые несколько раз (утекающие в поле) не могут быть выведены, без отслеживания полей + + 9. Метод, содержащий литеральные блоки, утекающие в неизвестность — полная блокировка вывода переменных, которые они пишут + + +Алгоритм анализатора: + 1. Делаем базовый проход без обратных ребер + 2. Если предыдущий проход вывел возврат по литеральному пути и метод не содержит блоков мутаторов — выход + 3. Делаем базовый проход с нетривиальными мутаторами + 4. Если есть литеральный возврат — выход + 5. Делаем индукционный переход + diff --git a/image/imageSource.st b/image/imageSource.st index 1362582..264d585 100644 --- a/image/imageSource.st +++ b/image/imageSource.st @@ -776,18 +776,115 @@ COMMENT CLASS BranchTest Test METHOD BranchTest -ifTrue |x| +ifBlock |x| + [ true ] ifTrue: [ x <- 123 ]. + [ x ] assertEq: 123 withComment: '1'. + + [ false ] ifFalse: [ x <- 151 ]. + [ x ] assertEq: 151 withComment: '2'. + [ true ] ifTrue: [ x <- 123 ] - ifFalse: [ x <- 151 ] - [ x ] assertEq: 123 -! + ifFalse: [ x <- 151 ]. + [ x ] assertEq: 123 withComment: '3'. + [ false ] ifFalse: [ x <- 521 ] + ifTrue: [ x <- 34 ]. + [ x ] assertEq: 521 withComment: '4'. +! METHOD BranchTest -ifFalse |x| +ifBlockBadCodegenIfTrue |x| [ false ] ifTrue: [ x <- 34 ] - ifFalse: [ x <- 521 ] - [ x ] assertEq: 521 + ifFalse: [ x <- 521 ]. + [ x ] assertEq: 521. +! + +METHOD BranchTest +ifBlockBadCodegenIfFalse |x| + [ true ] ifFalse: [ x <- 151 ] + ifTrue: [ x <- 123 ]. + [ x ] assertEq: 123. +! + +METHOD BranchTest +ifLiteral |x| + true ifTrue: [ x <- 123 ]. + [ x ] assertEq: 123 withComment: '1'. + + false ifFalse: [ x <- 151 ]. + [ x ] assertEq: 151 withComment: '2'. + + true ifTrue: [ x <- 123 ] + ifFalse: [ x <- 151 ]. + [ x ] assertEq: 123 withComment: '3'. + + false ifTrue: [ x <- 34 ] + ifFalse: [ x <- 521 ]. + [ x ] assertEq: 521 withComment: '4'. + + true ifFalse: [ x <- 151 ] + ifTrue: [ x <- 123 ]. + [ x ] assertEq: 123 withComment: '5'. + + false ifFalse: [ x <- 521 ] + ifTrue: [ x <- 34 ]. + [ x ] assertEq: 521 withComment: '6'. +! + +METHOD BranchTest +ifRealBooleanMethods |arr x| + Context new performTest: (Boolean methods at: #ifTrue:) + withArguments: (Array with: true with: [ x <- 123 ] + ). + [ x ] assertEq: 123 withComment: '1'. + + Context new performTest: (Boolean methods at: #ifFalse:) + withArguments: (Array with: false with: [ x <- 151 ] + ). + [ x ] assertEq: 151 withComment: '2'. + + Context new performTest: (Boolean methods at: #ifTrue:ifFalse:) + withArguments: (Array with: true with: [ x <- 123 ] with: [ x <- 151 ] + ). + [ x ] assertEq: 123 withComment: '3'. + + Context new performTest: (Boolean methods at: #ifTrue:ifFalse:) + withArguments: (Array with: false with: [ x <- 32 ] with: [ x <- 521 ] + ). + [ x ] assertEq: 521 withComment: '4'. + + Context new performTest: (Boolean methods at: #ifFalse:ifTrue:) + withArguments: (Array with: true with: [ x <- 151 ] with: [ x <- 123 ] + ). + [ x ] assertEq: 123 withComment: '5'. + + Context new performTest: (Boolean methods at: #ifFalse:ifTrue:) + withArguments: (Array with: false with: [ x <- 521 ] with: [ x <- 34 ] + ). + [ x ] assertEq: 521 withComment: '6'. +! + +METHOD BranchTest +ifRealTrueFalseMethods |arr x| + Context new performTest: (True methods at: #ifTrue:) + withArguments: (Array with: true with: [ x <- 123 ] + ). + [ x ] assertEq: 123 withComment: '1'. + + Context new performTest: (True methods at: #ifFalse:) + withArguments: (Array with: true with: [ x <- 151 ] + ). + [ x ] assertEq: 123 withComment: '2'. + + Context new performTest: (False methods at: #ifTrue:) + withArguments: (Array with: false with: [ x <- 151 ] + ). + [ x ] assertEq: 123 withComment: '3'. + + Context new performTest: (False methods at: #ifFalse:) + withArguments: (Array with: false with: [ x <- 151 ] + ). + [ x ] assertEq: 151 withComment: '4'. ! METHOD BranchTest @@ -1008,7 +1105,7 @@ init arrOfChars <- Array new: 257. 1 to: 257 do: [:idx| - arrOfChars at: idx put: (Char basicNew: idx-1) + arrOfChars at: idx put: (Char new: idx-1) ]. ! @@ -2057,7 +2154,6 @@ run4: rounds | list indices tree | METHOD Undefined main | command data x | - Char initialize. Class fillChildren. System fixMethodClasses. @@ -2072,35 +2168,40 @@ main | command data x | COMMENT -----------Boolean-------------- +METHOD MetaBoolean +new: arg + arg ifTrue: [ ^ true ]. + ^ false +! METHOD Boolean -and: aBlock - ^ self - ifTrue: [ aBlock value ] - ifFalse: [ false ] +ifTrue: aBlock + self ifTrue: [ ^ aBlock value ]. ! METHOD Boolean -or: aBlock - ^ self - ifTrue: [ true ] - ifFalse: [ aBlock value ] +ifFalse: aBlock + self ifFalse: [ ^ aBlock value ]. ! METHOD Boolean -not - ^ self - ifTrue: [ false ] - ifFalse: [ true ] +ifTrue: trueBlock ifFalse: falseBlock + self ifTrue: [ ^ trueBlock value ]. + self ifFalse:[ ^ falseBlock value ]. ! METHOD Boolean ifFalse: falseBlock ifTrue: trueBlock - ^ self ifTrue: [ trueBlock value ] ifFalse: [ falseBlock value ] + self ifTrue: [ ^ trueBlock value ]. + self ifFalse:[ ^ falseBlock value ]. ! METHOD Boolean -ifTrue: aBlock - ^ self ifTrue: [ aBlock value ] ifFalse: [ nil ] +and: aBlock + ^ self and: [ ^ Boolean new: aBlock value ] ! METHOD Boolean -ifFalse: aBlock - ^ self ifTrue: [ nil ] ifFalse: [ aBlock value ] +or: aBlock + ^ self or: [ ^ Boolean new: aBlock value ] +! +METHOD Boolean +not + ^ self not ! COMMENT -----------True-------------- METHOD MetaTrue @@ -2109,10 +2210,6 @@ new ^ true ! METHOD True -not - ^ false -! -METHOD True asString ^'true' ! @@ -2121,16 +2218,24 @@ printString ^self asString ! METHOD True -ifTrue: trueBlock ifFalse: falseBlock - ^ trueBlock value +ifTrue: aBlock + ^ aBlock value ! METHOD True -or: aBlock - ^ true +ifFalse: aBlock + ^ nil ! METHOD True and: aBlock - ^ aBlock value + ^ Boolean new: aBlock value +! +METHOD True +or: aBlock + ^ true +! +METHOD True +not + ^ false ! COMMENT -----------False-------------- METHOD MetaFalse @@ -2139,10 +2244,6 @@ new ^ false ! METHOD False -not - ^ true -! -METHOD False asString ^'false' ! @@ -2151,17 +2252,27 @@ printString ^self asString ! METHOD False -ifTrue: trueBlock ifFalse: falseBlock - ^ falseBlock value +ifTrue: aBlock + ^ nil ! METHOD False -or: aBlock - ^ aBlock value +ifFalse: aBlock + ^ aBlock value ! METHOD False and: aBlock ^ false ! +METHOD False +or: aBlock + ^ Boolean new: aBlock value +! +METHOD False +not + ^ true +! + +COMMENT -----------Thread-------------- METHOD MetaThread new: aBlock |instance| instance <- self new. @@ -2484,15 +2595,6 @@ args: argNames inst: instNames temp: tempNames ! COMMENT -----------Chars-------------- METHOD MetaChar -initialize - chars isNil ifTrue: [ - chars <- Array new: 257. - 1 to: 257 do: [:idx| - chars at: idx put: (Char basicNew: idx-1) - ] - ] -! -METHOD MetaChar basicNew: value " create and initialize a new char " ^ self in: self new at: 1 put: value @@ -2500,14 +2602,21 @@ basicNew: value METHOD MetaChar new: value " return unique Char for ASCII value (or EOF) " - (value < 257) ifTrue: [ ^ chars at: value+1 ]. - + (value < 257) ifTrue: [ + chars isNil ifTrue: [ + chars <- Array new: 257. + 1 to: 257 do: [:idx| + chars at: idx put: (Char basicNew: idx-1) + ] + ]. + ^ chars at: value+1 + ]. " otherwise build a custom Char " ^ self basicNew: value ! METHOD MetaChar newline - " return newline character " + " return newline character " ^ self new: 10 ! METHOD MetaChar diff --git a/include/Core.ll b/include/Core.ll index e4036d8..7c3c0b7 100644 --- a/include/Core.ll +++ b/include/Core.ll @@ -153,7 +153,7 @@ ;;;;;;;;;;;;;;;;;;;; functions ;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -define i1 @isSmallInteger(%TObject* %value) alwaysinline { +define i1 @isSmallInteger(%TObject* %value) alwaysinline nounwind { ; return reinterpret_cast(value) & 1; %int = ptrtoint %TObject* %value to i32 @@ -161,7 +161,7 @@ define i1 @isSmallInteger(%TObject* %value) alwaysinline { ret i1 %result } -define i32 @getIntegerValue(%TObject* %value) alwaysinline { +define i32 @getIntegerValue(%TObject* %value) alwaysinline nounwind { ; return (int32_t) (value >> 1) %int = ptrtoint %TObject* %value to i32 @@ -169,7 +169,7 @@ define i32 @getIntegerValue(%TObject* %value) alwaysinline { ret i32 %result } -define %TObject* @newInteger(i32 %value) alwaysinline { +define %TObject* @newInteger(i32 %value) alwaysinline nounwind { ; return reinterpret_cast( (value << 1) | 1 ); %shled = shl i32 %value, 1 @@ -178,7 +178,7 @@ define %TObject* @newInteger(i32 %value) alwaysinline { ret %TObject* %result } -define i32 @getSlotSize(i32 %fieldsCount) alwaysinline { +define i32 @getSlotSize(i32 %fieldsCount) alwaysinline nounwind { ;sizeof(TObject) + fieldsCount * sizeof(TObject*) %fieldsSize = mul i32 4, %fieldsCount @@ -188,21 +188,21 @@ define i32 @getSlotSize(i32 %fieldsCount) alwaysinline { } -define i32 @getObjectSize(%TObject* %this) alwaysinline { +define i32 @getObjectSize(%TObject* %this) alwaysinline nounwind { %1 = getelementptr %TObject* %this, i32 0, i32 0, i32 0 %data = load i32* %1 %result = lshr i32 %data, 2 ret i32 %result } -define %TObject* @setObjectSize(%TObject* %this, i32 %size) alwaysinline { +define %TObject* @setObjectSize(%TObject* %this, i32 %size) alwaysinline nounwind { %addr = getelementptr %TObject* %this, i32 0, i32 0, i32 0 %ssize = shl i32 %size, 2 store i32 %ssize, i32* %addr ret %TObject* %this } -define i1 @isObjectRelocated(%TObject* %this) alwaysinline { +define i1 @isObjectRelocated(%TObject* %this) alwaysinline nounwind { %1 = getelementptr %TObject* %this, i32 0, i32 0, i32 0 %data = load i32* %1 %field = and i32 %data, 1 @@ -210,7 +210,7 @@ define i1 @isObjectRelocated(%TObject* %this) alwaysinline { ret i1 %result } -define i1 @isObjectBinary(%TObject* %this) alwaysinline { +define i1 @isObjectBinary(%TObject* %this) alwaysinline nounwind { %1 = getelementptr %TObject* %this, i32 0, i32 0, i32 0 %data = load i32* %1 %field = and i32 %data, 2 @@ -218,14 +218,14 @@ define i1 @isObjectBinary(%TObject* %this) alwaysinline { ret i1 %result } -define %TClass** @getObjectClassPtr(%TObject* %this) alwaysinline { +define %TClass** @getObjectClassPtr(%TObject* %this) alwaysinline nounwind { %pclass = getelementptr inbounds %TObject* %this, i32 0, i32 1 ret %TClass** %pclass } @SmallInt = external global %TClass -define %TClass* @getObjectClass(%TObject* %this) alwaysinline { +define %TClass* @getObjectClass(%TObject* %this) alwaysinline nounwind { ; TODO SmallInt case %test = call i1 @isSmallInteger(%TObject* %this) br i1 %test, label %is_smallint, label %is_object @@ -237,36 +237,36 @@ is_object: ret %TClass* %class } -define %TObject* @setObjectClass(%TObject* %this, %TClass* %class) alwaysinline { +define %TObject* @setObjectClass(%TObject* %this, %TClass* %class) alwaysinline nounwind { %addr = call %TClass** @getObjectClassPtr(%TObject* %this) store %TClass* %class, %TClass** %addr ret %TObject* %this } -define %TObject** @getObjectFieldPtr(%TObject* %object, i32 %index) alwaysinline { +define %TObject** @getObjectFieldPtr(%TObject* %object, i32 %index) alwaysinline nounwind { %fields = getelementptr inbounds %TObject* %object, i32 0, i32 2 %fieldPtr = getelementptr inbounds [0 x %TObject*]* %fields, i32 0, i32 %index ret %TObject** %fieldPtr } -define %TObject** @getObjectFields(%TObject* %this) alwaysinline { +define %TObject** @getObjectFields(%TObject* %this) alwaysinline nounwind { %fieldsPtr = call %TObject** @getObjectFieldPtr(%TObject* %this, i32 0) ret %TObject** %fieldsPtr } -define %TObject* @getObjectField(%TObject* %object, i32 %index) alwaysinline { +define %TObject* @getObjectField(%TObject* %object, i32 %index) alwaysinline nounwind { %fieldPtr = call %TObject** @getObjectFieldPtr(%TObject* %object, i32 %index) %result = load %TObject** %fieldPtr ret %TObject* %result } -define %TObject** @setObjectField(%TObject* %object, i32 %index, %TObject* %value) alwaysinline { +define %TObject** @setObjectField(%TObject* %object, i32 %index, %TObject* %value) alwaysinline nounwind { %fieldPtr = call %TObject** @getObjectFieldPtr(%TObject* %object, i32 %index) store %TObject* %value, %TObject** %fieldPtr ret %TObject** %fieldPtr } -define %TObject* @getArgFromContext(%TContext* %context, i32 %index) alwaysinline { +define %TObject* @getArgFromContext(%TContext* %context, i32 %index) alwaysinline nounwind { %argsPtr = getelementptr inbounds %TContext* %context, i32 0, i32 2 %args = load %TObjectArray** %argsPtr %argsObj = bitcast %TObjectArray* %args to %TObject* @@ -274,7 +274,7 @@ define %TObject* @getArgFromContext(%TContext* %context, i32 %index) alwaysinlin ret %TObject* %arg } -define %TObject* @getLiteralFromContext(%TContext* %context, i32 %index) alwaysinline { +define %TObject* @getLiteralFromContext(%TContext* %context, i32 %index) alwaysinline nounwind { %methodPtr = getelementptr inbounds %TContext* %context, i32 0, i32 1 %method = load %TMethod** %methodPtr %literalsPtr = getelementptr inbounds %TMethod* %method, i32 0, i32 3 @@ -284,32 +284,32 @@ define %TObject* @getLiteralFromContext(%TContext* %context, i32 %index) alwaysi ret %TObject* %literal } -define %TObject* @getTempsFromContext(%TContext* %context) alwaysinline { +define %TObject* @getTempsFromContext(%TContext* %context) alwaysinline nounwind { %tempsPtr = getelementptr inbounds %TContext* %context, i32 0, i32 3 %temps = load %TObjectArray** %tempsPtr %tempsObj = bitcast %TObjectArray* %temps to %TObject* ret %TObject* %tempsObj } -define %TObject* @getTemporaryFromContext(%TContext* %context, i32 %index) alwaysinline { +define %TObject* @getTemporaryFromContext(%TContext* %context, i32 %index) alwaysinline nounwind { %temps = call %TObject* @getTempsFromContext(%TContext* %context) %temporary = call %TObject* @getObjectField(%TObject* %temps, i32 %index) ret %TObject* %temporary } -define void @setTemporaryInContext(%TContext* %context, i32 %index, %TObject* %value) alwaysinline { +define void @setTemporaryInContext(%TContext* %context, i32 %index, %TObject* %value) alwaysinline nounwind { %temps = call %TObject* @getTempsFromContext(%TContext* %context) call %TObject** @setObjectField(%TObject* %temps, i32 %index, %TObject* %value) ret void } -define %TObject* @getInstanceFromContext(%TContext* %context, i32 %index) alwaysinline { +define %TObject* @getInstanceFromContext(%TContext* %context, i32 %index) alwaysinline nounwind { %self = call %TObject* @getArgFromContext(%TContext* %context, i32 0) %instance = call %TObject* @getObjectField(%TObject* %self, i32 %index) ret %TObject* %instance } -define void @setInstanceInContext(%TContext* %context, i32 %index, %TObject* %value) alwaysinline { +define void @setInstanceInContext(%TContext* %context, i32 %index, %TObject* %value) alwaysinline nounwind { %self = call %TObject* @getArgFromContext(%TContext* %context, i32 0) %instancePtr = call %TObject** @getObjectFieldPtr(%TObject* %self, i32 %index) call void @checkRoot(%TObject* %value, %TObject** %instancePtr) @@ -317,7 +317,7 @@ define void @setInstanceInContext(%TContext* %context, i32 %index, %TObject* %va ret void } -define void @dummy() gc "shadow-stack" { +define void @dummy() nounwind gc "shadow-stack" { ; enabling shadow stack init on this module ret void } @@ -336,7 +336,7 @@ declare %TByteObject* @newBinaryObject(%TClass*, i32) ;declare %TObject* @sendMessage(%TContext* %callingContext, %TSymbol* %selector, %TObjectArray* %arguments, %TClass* %receiverClass, i32 %callSiteOffset) declare { %TObject*, %TContext* } @sendMessage(%TContext* %callingContext, %TSymbol* %selector, %TObjectArray* %arguments, %TClass* %receiverClass, i32 %callSiteOffset) -declare %TBlock* @createBlock(%TContext* %callingContext, i8 %argLocation, i16 %bytePointer) +declare %TBlock* @createBlock(%TContext* %callingContext, i8 %argLocation, i16 %bytePointer, i32 %stackTop) declare { %TObject*, %TContext* } @invokeBlock(%TBlock* %block, %TContext* %callingContext) ;declare %TObject* @invokeBlock(%TBlock* %block, %TContext* %callingContext) @@ -350,6 +350,8 @@ declare %TObject* @callPrimitive(i8 %opcode, %TObjectArray* %args, i1* %primitiv %TContext* ; targetContext } +declare i32 @printf(i8* noalias nocapture, ...) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;; exception API ;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/include/analysis.h b/include/analysis.h index 641f27e..8a7e0ca 100644 --- a/include/analysis.h +++ b/include/analysis.h @@ -3,6 +3,7 @@ #include #include +#include #include @@ -105,6 +106,7 @@ class ControlNode { // Dynamically cast node to a specified type. // If type does not match null is returned. template T* cast(); + template const T* cast() const; uint32_t getIndex() const { return m_index; } @@ -127,7 +129,7 @@ class ControlNode { void setValue(llvm::Value* value) { m_value = value; } llvm::Value* getValue() const { return m_value; } - // Get a list of nodes which refer current node as argument + // Get a list of nodes which refer current node as argument and tau nodes void addConsumer(ControlNode* consumer) { m_consumers.insert(consumer); } void removeConsumer(ControlNode* consumer) { m_consumers.erase(consumer); } const TNodeSet& getConsumers() const { return m_consumers; } @@ -143,10 +145,12 @@ class ControlNode { TNodeSet m_consumers; }; +class TauNode; + // Instruction node represents a signle VM instruction and it's relations in code. class InstructionNode : public ControlNode { public: - InstructionNode(uint32_t index) : ControlNode(index), m_instruction(opcode::extended) { } + InstructionNode(uint32_t index) : ControlNode(index), m_instruction(opcode::extended), m_tau(0) { } virtual TNodeType getNodeType() const { return ntInstruction; } void setInstruction(TSmalltalkInstruction instruction) { m_instruction = instruction; } @@ -175,9 +179,13 @@ class InstructionNode : public ControlNode { iterator begin() { return m_arguments.begin(); } iterator end() { return m_arguments.end(); } + TauNode* getTauNode() const { return m_tau; } + void setTauNode(TauNode* value) { m_tau = value; } + private: TSmalltalkInstruction m_instruction; TArgumentList m_arguments; + TauNode* m_tau; }; // PushBlockNode represents a single PushBlock instruction. @@ -194,6 +202,21 @@ class PushBlockNode : public InstructionNode { ParsedBlock* m_parsedBlock; }; +class BranchNode : public InstructionNode { +public: + BranchNode(uint32_t index) : InstructionNode(index), m_targetNode(0), m_skipNode(0) {} + + ControlNode* getTargetNode() const { return m_targetNode; } + ControlNode* getSkipNode() const { return m_skipNode; } + + void setTargetNode(ControlNode* value) { m_targetNode = value; } + void setSkipNode(ControlNode* value) { m_skipNode = value; } + +private: + ControlNode* m_targetNode; + ControlNode* m_skipNode; +}; + // Phi node act as a value aggregator from several domains. // When value is pushed on the stack in one basic block and // popped in another we say that actual values have a stack relation. @@ -221,13 +244,13 @@ class PhiNode : public ControlNode { void addIncoming(ControlDomain* domain, ControlNode* value) { TIncoming incoming; incoming.domain = domain; - incoming.node = value; + incoming.node = value; m_incomingList.push_back(incoming); } const TIncomingList& getIncomingList() const { return m_incomingList; } - TNodeSet getRealValues(); + TNodeSet getRealValues() const; llvm::PHINode* getPhiValue() const { return m_phiValue; } void setPhiValue(llvm::PHINode* value) { m_phiValue = value; } @@ -242,8 +265,46 @@ class PhiNode : public ControlNode { // It will link variable type transitions across a method. class TauNode : public ControlNode { public: - TauNode(uint32_t index) : ControlNode(index) { } + TauNode(uint32_t index) : ControlNode(index), m_kind(tkUnknown) { } virtual TNodeType getNodeType() const { return ntTau; } + + typedef std::map TIncomingMap; + typedef std::map TIncomingIndexMap; + + void addIncoming(ControlNode* node, bool byBackEdge = false) { + m_incomingMap[node] = byBackEdge; + node->addConsumer(this); + } + + const TIncomingMap& getIncomingMap() const { return m_incomingMap; } + + enum TKind { + tkUnknown = 0, + tkProvider, + tkAggregator, + tkClosure + }; + + void setKind(TKind value) { m_kind = value; } + TKind getKind() const { return m_kind; } + +private: + TIncomingMap m_incomingMap; + TKind m_kind; +}; + +class ClosureTauNode : public TauNode { +public: + ClosureTauNode(uint32_t index) : TauNode(index), m_origin(0) { } + + typedef std::size_t TIndex; + typedef std::vector TIndexList; + + InstructionNode* getOrigin() const { return m_origin; } + void setOrigin(InstructionNode* node) { m_origin = node; } + +private: + InstructionNode* m_origin; }; // Domain is a group of nodes within a graph @@ -273,9 +334,10 @@ class ControlDomain { ControlNode* topValue(bool keep = false) { assert(! m_localStack.empty()); - ControlNode* value = m_localStack.back(); + ControlNode* const value = m_localStack.back(); if (!keep) m_localStack.pop_back(); + return value; } @@ -289,13 +351,17 @@ class ControlDomain { argument->addEdge(forNode); } else { - m_reqestedArguments.push_back((TArgumentRequest){index, forNode}); + m_reqestedArguments.push_back(TArgumentRequest(index, forNode, keep)); } } struct TArgumentRequest { std::size_t index; InstructionNode* requestingNode; + bool keep; + + TArgumentRequest(std::size_t index, InstructionNode* requestingNode, bool keep) + : index(index), requestingNode(requestingNode), keep(keep) {} }; typedef std::vector TRequestList; @@ -320,7 +386,10 @@ class ControlGraph { : m_parsedMethod(parsedMethod), m_parsedBlock(0), m_lastNodeIndex(0) { } ControlGraph(ParsedMethod* parsedMethod, ParsedBlock* parsedBlock) - : m_parsedMethod(parsedMethod), m_parsedBlock(parsedBlock), m_lastNodeIndex(0) { } + : m_parsedMethod(parsedMethod), m_parsedBlock(parsedBlock), m_lastNodeIndex(0) + { + m_metaInfo.isBlock = true; + } ParsedMethod* getParsedMethod() const { return m_parsedMethod; } @@ -337,8 +406,12 @@ class ControlGraph { typedef std::list TNodeList; typedef TNodeList::iterator nodes_iterator; + typedef TNodeList::reverse_iterator reverse_iterator; nodes_iterator nodes_begin() { return m_nodes.begin(); } nodes_iterator nodes_end() { return m_nodes.end(); } + reverse_iterator nodes_rbegin() { return m_nodes.rbegin(); } + reverse_iterator nodes_rend() { return m_nodes.rend(); } + bool isEmpty() const { return m_nodes.begin() == m_nodes.end(); } ControlNode* newNode(ControlNode::TNodeType type) { assert(type == ControlNode::ntInstruction @@ -386,6 +459,8 @@ class ControlGraph { delete node; } + void eraseTauNodes(); + ~ControlGraph() { TDomainSet::iterator iDomain = m_domains.begin(); while (iDomain != m_domains.end()) @@ -410,6 +485,67 @@ class ControlGraph { return iDomain->second; } + struct TEdge { + const InstructionNode* from; + const InstructionNode* to; + + TEdge(const InstructionNode* from, const InstructionNode* to) + : from(from), to(to) + { + assert(from); + assert(to); + } + }; + + class EdgeCompare { + public: + bool operator() (const TEdge& a, const TEdge& b) const { + if (a.from < b.from) + return true; + + if (a.from > b.from) + return false; + + return a.to < b.to; + } + }; + + typedef std::set TEdgeSet; + + struct TMetaInfo { + bool isBlock; + bool hasBlockReturn; + bool hasLiteralBlocks; + bool hasMutatingBlocks; + + bool hasLoops; + bool hasBackEdgeTau; + + bool usesSelf; + bool usesSuper; + + bool readsFields; + bool writesFields; + + bool hasPrimitive; + + TEdgeSet backEdges; + + typedef std::vector TIndexList; + TIndexList readsTemporaries; + TIndexList writesTemporaries; + TIndexList readsArguments; + + static void insertIndex(std::size_t index, TIndexList& list) { + if (std::find(list.begin(), list.end(), index) == list.end()) + list.push_back(index); + } + + TMetaInfo(); + }; + + TMetaInfo& getMeta() { return m_metaInfo; } + private: ParsedMethod* m_parsedMethod; ParsedBlock* m_parsedBlock; @@ -420,6 +556,8 @@ class ControlGraph { typedef std::map TDomainMap; TDomainMap m_blocksToDomains; + + TMetaInfo m_metaInfo; }; template<> InstructionNode* ControlNode::cast(); @@ -432,6 +570,15 @@ template<> TauNode* ControlGraph::newNode(); template<> PushBlockNode* ControlNode::cast(); template<> PushBlockNode* ControlGraph::newNode(); +template<> const PushBlockNode* ControlNode::cast() const; + +template<> BranchNode* ControlNode::cast(); +template<> const BranchNode* ControlNode::cast() const; +template<> BranchNode* ControlGraph::newNode(); + +template<> ClosureTauNode* ControlNode::cast(); +template<> ClosureTauNode* ControlGraph::newNode(); +template<> const ClosureTauNode* ControlNode::cast() const; class DomainVisitor { public: @@ -441,6 +588,8 @@ class DomainVisitor { virtual bool visitDomain(ControlDomain& /*domain*/) { return true; } virtual void domainsVisited() { } + ControlGraph& getGraph() { return *m_graph; } + void run() { ControlGraph::iterator iDomain = m_graph->begin(); const ControlGraph::iterator iEnd = m_graph->end(); @@ -457,7 +606,7 @@ class DomainVisitor { } } -protected: +private: ControlGraph* const m_graph; }; @@ -518,9 +667,14 @@ class GraphWalker { GraphWalker() { } virtual ~GraphWalker() { } - void addStopNode(ControlNode* node) { m_stopNodes.insert(node); } - void addStopNodes(const TNodeSet& nodes) { m_stopNodes.insert(nodes.begin(), nodes.end()); } - void resetStopNodes() { m_stopNodes.clear(); } + void resetStopNodes() { m_colorMap.clear(); } + void addStopNode(ControlNode* node) { m_colorMap[node] = ncBlack; } + + void addStopNodes(const TNodeSet& nodes) { + TNodeSet::const_iterator iNode = nodes.begin(); + for (; iNode != nodes.end(); ++iNode) + m_colorMap[*iNode] = ncBlack; + } enum TVisitResult { vrKeepWalking = 0, @@ -528,7 +682,15 @@ class GraphWalker { vrStopWalk }; - virtual TVisitResult visitNode(ControlNode* node) = 0; + struct TPathNode { + const ControlNode* const node; + const TPathNode* const prev; + + TPathNode(const ControlNode* node = 0, const TPathNode* prev = 0) + : node(node), prev(prev) {} + }; + + virtual TVisitResult visitNode(ControlNode& node, const TPathNode* path) = 0; virtual void nodesVisited() { } enum TWalkDirection { @@ -536,31 +698,80 @@ class GraphWalker { wdBackward }; - void run(ControlNode* startNode, TWalkDirection direction) { + enum TWalkType { + wtDepthFirst, + wtBreadthFirst + }; + + void run(ControlNode* startNode, TWalkDirection direction, TWalkType type, bool visitStart) { assert(startNode); m_direction = direction; + m_type = type; + + if (type == wtDepthFirst) + depthRun(*startNode, visitStart); + else + breadthRun(*startNode, visitStart); - m_stopNodes.erase(startNode); - walkIn(startNode); nodesVisited(); } private: - bool walkIn(ControlNode* currentNode) { + void depthRun(ControlNode& startNode, bool visitStart) { + TPathNode path(&startNode); + + if (visitStart && visitNode(startNode, &path) != vrKeepWalking) + return; + + walkIn(&startNode, &path); + } + + void breadthRun(ControlNode& startNode, bool visitStart) { + TNodeQueue queue; + + if (visitStart) + queue.push_back(&startNode); + else + enqueueNode(startNode, queue); + + walkQueue(queue); + } + +protected: + enum TNodeColor { + ncWhite = 0, // unvisited node + ncGrey, // node in progress + ncBlack // visited and settled node + }; + + typedef std::map TColorMap; + + TNodeColor getNodeColor(ControlNode* node) const { + TColorMap::const_iterator iColor = m_colorMap.find(node); + if (iColor != m_colorMap.end()) + return iColor->second; + else + return ncWhite; + } + +private: + bool walkIn(ControlNode* currentNode, const TPathNode* path) { + m_colorMap[currentNode] = ncGrey; + const TNodeSet& nodes = (m_direction == wdForward) ? currentNode->getOutEdges() : currentNode->getInEdges(); - for (TNodeSet::iterator iNode = nodes.begin(); iNode != nodes.end(); ++iNode) { + for (TNodeSet::const_iterator iNode = nodes.begin(); iNode != nodes.end(); ++iNode) { ControlNode* const node = *iNode; - if (m_stopNodes.find(node) != m_stopNodes.end()) + if (getNodeColor(node) != ncWhite) continue; - else - m_stopNodes.insert(node); - switch (const TVisitResult result = visitNode(node)) { + const TPathNode newPath(node, path); + + switch (const TVisitResult result = visitNode(*node, &newPath)) { case vrKeepWalking: - if (!walkIn(node)) + if (!walkIn(node, &newPath)) return false; break; @@ -572,20 +783,49 @@ class GraphWalker { } } + m_colorMap[currentNode] = ncBlack; return true; } + typedef std::list TNodeQueue; + + void walkQueue(TNodeQueue& queue) { + while (! queue.empty()) { + ControlNode& currentNode = *queue.front(); queue.pop_front(); + + if (getNodeColor(¤tNode) == ncBlack) + continue; + + switch (visitNode(currentNode, 0)) { + case vrStopWalk: + return; + + case vrKeepWalking: + enqueueNode(currentNode, queue); + + case vrSkipPath: + break; + } + + m_colorMap[¤tNode] = ncBlack; + } + } + + void enqueueNode(const ControlNode& node, TNodeQueue& queue) { + const TNodeSet& nodes = (m_direction == wdForward) ? + node.getOutEdges() : node.getInEdges(); + + for (TNodeSet::const_iterator iNode = nodes.begin(); iNode != nodes.end(); ++iNode) + queue.push_back(*iNode); + } + private: TWalkDirection m_direction; - TNodeSet m_stopNodes; -}; - -class ForwardWalker : public GraphWalker { -public: - void run(ControlNode* startNode) { GraphWalker::run(startNode, wdForward); } + TWalkType m_type; + TColorMap m_colorMap; }; -class PathVerifier : public ForwardWalker { +class PathVerifier : public GraphWalker { public: PathVerifier(const TNodeSet& destinationNodes) : m_destinationNodes(destinationNodes), m_verified(false) {} @@ -597,15 +837,15 @@ class PathVerifier : public ForwardWalker { assert(startNode); m_verified = false; - ForwardWalker::run(startNode); + GraphWalker::run(startNode, wdForward, wtDepthFirst, true); } private: - virtual TVisitResult visitNode(ControlNode* node) { + virtual TVisitResult visitNode(ControlNode& node, const TPathNode*) { // Checking if there is a path between // start node and any of the destination nodes. - if (m_destinationNodes.find(node) != m_destinationNodes.end()) { + if (m_destinationNodes.find(&node) != m_destinationNodes.end()) { m_verified = true; return vrStopWalk; } @@ -618,7 +858,90 @@ class PathVerifier : public ForwardWalker { bool m_verified; }; +class BackEdgeDetector : public GraphWalker { +public: + typedef ControlGraph::TEdge TEdge; + typedef ControlGraph::TEdgeSet TEdgeSet; + + const TEdgeSet& getBackEdges() const { return m_backEdges; } + + void run(ControlGraph& graph) { + m_backEdges.clear(); + + if (graph.nodes_begin() == graph.nodes_end()) + return; + + GraphWalker::run(*graph.nodes_begin(), GraphWalker::wdForward, wtDepthFirst, true); + } + +protected: + virtual TVisitResult visitNode(ControlNode& node, const TPathNode*) { + if (BranchNode* const branch = node.cast()) { + InstructionNode* const target = branch->getTargetNode()->cast(); + assert(target); + + if (getNodeColor(target) == ncGrey) + m_backEdges.insert(TEdge(branch, target)); + } + + return vrKeepWalking; + } + +private: + TEdgeSet m_backEdges; +}; + +class TauLinker : private BackEdgeDetector { +public: + TauLinker(ControlGraph& graph) : m_graph(graph) {} + void run() { BackEdgeDetector::run(m_graph); } + + void addClosureNode( + const InstructionNode& node, + const ClosureTauNode::TIndexList& readIndices, + const ClosureTauNode::TIndexList& writeIndices); + + struct TClosureInfo { + ClosureTauNode::TIndexList readIndices; + ClosureTauNode::TIndexList writeIndices; + + bool writesIndex(ClosureTauNode::TIndex index) const { + return std::find(writeIndices.begin(), writeIndices.end(), index) != writeIndices.end(); + } + }; + + typedef std::map TClosureMap; + const TClosureMap& getClosures() const { return m_closures; } + + void eraseTauNodes(); + void reset(); + +private: + virtual GraphWalker::TVisitResult visitNode(ControlNode& node, const TPathNode* path); + virtual void nodesVisited(); + +private: + void optimizeTau(); + void eraseRedundantTau(); + void detectRedundantTau(); + + void createType(InstructionNode& instruction); + void processPushTemporary(InstructionNode& instruction); + void processClosure(InstructionNode& instruction); + +private: + ControlGraph& m_graph; + ControlGraph& getGraph() { return m_graph; } + + typedef std::set TInstructionSet; + TInstructionSet m_pendingNodes; + + typedef std::list TTauList; + TTauList m_providers; + + TClosureMap m_closures; +}; } // namespace st diff --git a/include/inference.h b/include/inference.h new file mode 100644 index 0000000..4cbff36 --- /dev/null +++ b/include/inference.h @@ -0,0 +1,593 @@ +#ifndef LLST_INFERENCE_H_INCLUDED +#define LLST_INFERENCE_H_INCLUDED + +#include +#include + +#include + +namespace type { + + +class Type { +public: + enum TKind { + tkUndefined = 0, + tkLiteral, + tkMonotype, + tkComposite, + tkArray, + tkPolytype + // TODO tkBlock + }; + + enum TBlockSubtypes { + bstOrigin = 0, + bstOffset, + bstArgIndex, + bstContextIndex, + bstReadsTemps, + bstWritesTemps, + bstCaptureIndex + }; + + // Return a string representation of a type: + // Kind Representation Example + // tkUndefined ? ? + // tkPolytype * * + // tkLiteral literal value 42 + // tkMonotype (class name) (SmallInt) + // tkComposite (class name, ...) (SmallInt, *) + // tkArray class name [...] Array[String, *, (*, *), (True, False)] + std::string toString(bool subtypesOnly = true) const; + + Type(TKind kind = tkUndefined) : m_kind(kind), m_value(0) {} + Type(TObject* literal, TKind kind = tkLiteral) { set(literal, kind); } + Type(TClass* klass, TKind kind = tkMonotype) { set(klass, kind); } + + Type(const Type& copy) : m_kind(copy.m_kind), m_value(copy.m_value), m_subTypes(copy.m_subTypes) {} + + void setKind(TKind kind) { m_kind = kind; } + TKind getKind() const { return m_kind; } + TObject* getValue() const { return m_value; } + + typedef std::vector TSubTypes; + + void reset() { + m_kind = tkUndefined; + m_value = 0; + m_subTypes.clear(); + } + + void set(TObject* literal, TKind kind = tkLiteral) { + m_kind = kind; + m_value = literal; + } + + void set(TClass* klass, TKind kind = tkMonotype) { + m_kind = kind; + m_value = klass; + } + + bool isUndefined() const { return m_kind == tkUndefined; } + bool isLiteral() const { return m_kind == tkLiteral; } + bool isMonotype() const { return m_kind == tkMonotype; } + bool isComposite() const { return m_kind == tkComposite; } + bool isArray() const { return m_kind == tkArray; } + bool isPolytype() const { return m_kind == tkPolytype; } + + bool isBlock() const { + return + isMonotype() && + (m_value == globals.blockClass) && + !m_subTypes.empty(); + } + + const TSubTypes& getSubTypes() const { return m_subTypes; } + + Type& pushSubType(const Type& type) { m_subTypes.push_back(type); return m_subTypes.back(); } + + void addSubType(const Type& type) { + if (std::find(m_subTypes.begin(), m_subTypes.end(), type) == m_subTypes.end()) + m_subTypes.push_back(type); + } + + Type flatten() const; + + Type fold() const { + if (m_kind != tkComposite) + return *this; + + const std::size_t subtypesCount = m_subTypes.size(); + if (! subtypesCount) + return *this; + + Type result = m_subTypes.at(0); + for (std::size_t i = 1; i < subtypesCount; i++) { + if (m_subTypes.at(i).m_kind == tkComposite) + result &= m_subTypes.at(i).fold(); + else + result &= m_subTypes.at(i); + } + + return result; + } + + const Type& operator [] (std::size_t index) const { return m_subTypes[index]; } + Type& operator [] (std::size_t index) { return m_subTypes[index]; } + + bool operator < (const Type& other) const { + if (m_kind != other.m_kind) + return m_kind < other.m_kind; + + if (m_value != other.m_value) + return m_value < other.m_value; + + if (m_subTypes.size() != other.m_subTypes.size()) + return m_subTypes.size() < other.m_subTypes.size(); + + for (std::size_t index = 0; index < m_subTypes.size(); index++) { + if (m_subTypes[index] < other.m_subTypes[index]) + return true; + else if (other.m_subTypes[index] < m_subTypes[index]) + return false; + } + + return false; + } + + bool operator == (const Type& other) const { + if (m_kind != other.m_kind) + return false; + + if (m_value != other.m_value) + return false; + + if (m_subTypes != other.m_subTypes) + return false; + + return true; + } + + Type& operator = (const Type& other) { + m_kind = other.m_kind; + m_value = other.m_value; + m_subTypes = other.m_subTypes; + + return *this; + } + + Type operator | (const Type& other) const { return Type(*this) |= other; } + Type operator & (const Type& other) const { return Type(*this) &= other; } + + // ? | _ -> _ + // * | _ -> (*, _) + + // 1 | 1 -> 1 + // 1 | 2 -> (1, 2) + // A | B -> (A, B) + // (A) | (B) -> (A, B) + // (A) | (B,C) -> (A, B, C) + // Block1 | Block2 -> (Block1, Block2) + Type& operator |= (const Type& other) { + if (*this == other) + return *this; + + if (m_kind != tkComposite) { + Type composite(tkComposite); + composite.addSubType(*this); + *this = composite; + } + + if (other.m_value == globals.blockClass) { + addSubType(other); + return *this; + } + + if (other.m_kind == tkComposite) { + for (std::size_t index = 0; index < other.m_subTypes.size(); index++) + addSubType(other[index]); + + return *this; + } + + return *this; + } + + // ? & _ -> ? + // * & _ -> * + + // 2 & 2 -> 2 + // 2 & 3 -> (SmallInt) + // 2 & (SmallInt) -> (SmallInt) + // true & false -> (Boolean) + + // (2, 3) & (SmallInt) -> (SmallInt) + // (SmallInt) & (SmallInt) -> (SmallInt) + // (SmallInt) & (SmallInt) -> (SmallInt) + // (SmallInt) & true -> * + // (SmallInt) & (Object) -> * + + // Array[2,3] & (Array) -> (Array) + // + Type& operator &= (const Type& other) { + if (other.m_kind == tkUndefined || other.m_kind == tkPolytype) + return *this = Type((m_kind == tkUndefined) ? tkUndefined : other.m_kind); + + switch (m_kind) { + case tkUndefined: + case tkPolytype: + return *this = Type((other.m_kind == tkUndefined) ? tkUndefined : m_kind); + + case tkLiteral: + if (m_value == other.m_value) { // 2 & 3 + return *this; // 2 & 2 -> 2 + } else { + TClass* const leftClass = isSmallInteger(m_value) ? globals.smallIntClass : m_value->getClass(); + + if (other.m_kind == tkMonotype) { // 2 & (SmallInt) + TClass* const rightClass = static_cast(other.m_value); + if (leftClass == rightClass) { + // 2 & (SmallInt) -> (SmallInt) + return *this = (Type(leftClass) &= other); + } else { + // String & (SmallInt) -> * + return *this = Type(tkPolytype); + } + } else { // literal & literal + TClass* const rightClass = isSmallInteger(other.m_value) ? globals.smallIntClass : other.m_value->getClass(); + if (leftClass == rightClass) { + // 2 & 3 -> (SmallInt) + return *this = Type(leftClass); + } + TClass* const booleanClass = globals.trueObject->getClass()->parentClass; + if (leftClass->parentClass == rightClass->parentClass && leftClass->parentClass == booleanClass) { + // true & false -> Boolean + return *this = Type(booleanClass); + } + } + + return *this = (Type(leftClass) &= other); + } + + case tkMonotype: { + if (m_value == other.m_value) // (SmallInt) & (Object) + return *this; // (SmallInt) & (SmallInt) + + TObject* const otherValue = other.m_value; + TClass* const otherKlass = isSmallInteger(otherValue) ? globals.smallIntClass : otherValue->getClass(); + if (other.m_kind == tkLiteral && m_value == otherKlass) + return *this; // (SmallInt) & 42 + else + return *this = Type(tkPolytype); + } + + case tkArray: + if (other.m_kind == tkArray) { // Array[2, 3] & Array[2, 3] + if (m_value == other.m_value) { // Array[true, false] & Object[true, false] + if (*this == other) + return *this; + else + return *this = Type(m_value, tkMonotype); // Array[2, 3] & (Array) + } + } + return *this = (Type(m_value) &= other); + + case tkComposite: + if (m_subTypes.empty()) { + reset(); + return *this; + } + + Type& result = m_subTypes[0]; + for (std::size_t index = 1; index < m_subTypes.size(); index++) { + result &= m_subTypes[index]; + + if (result.getKind() == tkUndefined || result.getKind() == tkPolytype) + return *this = result; + } + + return *this = result; + } + + reset(); + return *this; + } + +private: + typedef std::set TTypeSet; + void flattenInto(TTypeSet& typeSet) const; + +private: + TKind m_kind; + TObject* m_value; + TSubTypes m_subTypes; +}; + +typedef std::size_t TNodeIndex; +typedef std::map TTypeMap; + +inline std::string getQualifiedMethodName(TMethod* method, const Type& arguments) { + return + arguments.toString() + "::" + + method->klass->name->toString() + ">>" + + method->name->toString(); +} + +class InferContext { +public: + InferContext(TMethod* method, std::size_t index, const Type& arguments) : + m_method(method), + m_index(index), + m_arguments(arguments), + m_returnType(Type::tkComposite), + m_recursionKind(rkUnknown) + {} + + std::string getQualifiedName() const { return getQualifiedMethodName(m_method, m_arguments); } + + TMethod* getMethod() const { return m_method; } + std::size_t getIndex() const { return m_index; } + + const Type& getArgument(std::size_t index) const { + static const Type polytype(Type::tkPolytype); + + if (m_arguments.getKind() != Type::tkPolytype) + return m_arguments[index]; + else + return polytype; + } + + const Type& getArguments() const { return m_arguments; } + const TTypeMap& getTypes() const { return m_types; } + void resetTypes() { m_types.clear(); } + + Type& getRawReturnType() { return m_returnType; } + + const Type& getReturnType() const { + const std::size_t subtypesCount = m_returnType.getSubTypes().size(); + return (subtypesCount == 1) ? m_returnType[0] : m_returnType; + } + + Type getSingleReturnType() const { return m_returnType.fold(); } + + Type& getInstructionType(TNodeIndex index) { return m_types[index]; } + Type& operator[] (TNodeIndex index) { return m_types[index]; } + Type& operator[] (const st::ControlNode& node) { return getInstructionType(node.getIndex()); } + + // variable index -> aggregated type + typedef std::size_t TVariableIndex; + typedef std::map TVariableMap; + + // capture site index -> captured context types + typedef std::size_t TSiteIndex; + typedef std::map TBlockClosures; + + TBlockClosures& getBlockClosures() { return m_blockClosures; } + void resetClosures() { m_blockClosures.clear(); } + + enum TRecursionKind { + rkUnknown = 0, + rkYes, + rkNo + }; + + TRecursionKind getRecursionKind() const { return m_recursionKind; } + void setRecursionKind(TRecursionKind value) { m_recursionKind = value; } + + + class ContextCompare { + public: + bool operator() (const InferContext* a, const InferContext* b) const { + return a->getIndex() < b->getIndex(); + } + }; + + typedef std::set TContextSet; + TContextSet& getReferredContexts() { return m_referredContexts; } + const TContextSet& getReferredContexts() const { return m_referredContexts; } + +private: + TMethod* const m_method; + const std::size_t m_index; + const Type m_arguments; + TTypeMap m_types; + Type m_returnType; + + TBlockClosures m_blockClosures; + TRecursionKind m_recursionKind; + TContextSet m_referredContexts; +}; + +struct TContextStack { + InferContext& context; + TContextStack* parent; + + TContextStack(InferContext& context, TContextStack* parent = 0) + : context(context), parent(parent) {} +}; + +class TypeSystem { +public: + TypeSystem(SmalltalkVM& vm) : + m_vm(vm), + m_graphCache(), + m_contextCache(), + m_blockCache(), + m_blockGraphCache(), + m_lastContextIndex(1) + {} + + ~TypeSystem() { + for(TBlockGraphCache::const_iterator it = m_blockGraphCache.begin(); it != m_blockGraphCache.end(); ++it) { + delete it->second; + } + for(TBlockCache::const_iterator it = m_blockCache.begin(); it != m_blockCache.end(); ++it) { + delete it->second; + } + for(TContextCache::const_iterator it = m_contextCache.begin(); it != m_contextCache.end(); ++it) { + const TContextMap& contextMap = it->second; + for(TContextMap::const_iterator jt = contextMap.begin(); jt != contextMap.end(); ++jt) { + delete jt->second; + } + } + for(TGraphCache::const_iterator it = m_graphCache.begin(); it != m_graphCache.end(); ++it) { + const TGraphEntry& entry = it->second; + delete entry.first; + delete entry.second; + } + } + + typedef TSymbol* TSelector; + + InferContext* findBlockContext(std::size_t contextIndex) const; + + InferContext* inferMessage( + TSelector selector, + const Type& arguments, + TContextStack* parent, + bool sendToSuper = false); + + InferContext* inferBlock(Type& block, const Type& arguments, TContextStack* parent); + InferContext* inferDynamicBlock(Type& block, const Type& arguments, const Type& temporaries, TContextStack* parent); + + // TODO Solve concurrent modifications by returning: + // - R/O holder to the shared graph + // - R/W holder to the exclusive copy + st::ControlGraph* getMethodGraph(TMethod* method); + st::ControlGraph* getBlockGraph(st::ParsedBlock* parsedBlock); + + void dumpAllContexts() const; + void drawCallGraph() const; + +private: + void doInferBlock(InferContext* inferContext, Type& block, const Type& arguments, TContextStack* parent); + +private: + typedef std::pair TGraphEntry; + typedef std::map TGraphCache; + + typedef std::map TContextMap; + typedef std::map TContextCache; + + // [Block, Args] -> block context + typedef std::map TBlockCache; + + typedef std::map TBlockGraphCache; + +private: + SmalltalkVM& m_vm; // TODO Image must be enough + + TGraphCache m_graphCache; + TContextCache m_contextCache; + TBlockCache m_blockCache; + TBlockGraphCache m_blockGraphCache; + + std::size_t m_lastContextIndex; +}; + +class TypeAnalyzer { +public: + TypeAnalyzer(TypeSystem& system, st::ControlGraph& graph, TContextStack& contextStack) : + m_system(system), + m_graph(graph), + m_contextStack(contextStack), + m_context(contextStack.context), + m_tauLinker(m_graph), + m_walker(*this) + { + } + + void run(const Type* blockType = 0); + +private: + void dumpTypes(const InferContext& context); + std::string getMethodName(); + bool basicRun(); + + void processInstruction(st::InstructionNode& instruction); + void processTau(const st::TauNode& tau); + + Type& processPhi(const st::PhiNode& phi); + Type& getArgumentType(const st::InstructionNode& instruction, std::size_t index = 0); + + void walkComplete(); + + void doPushConstant(const st::InstructionNode& instruction); + void doPushLiteral(const st::InstructionNode& instruction); + void doPushArgument(const st::InstructionNode& instruction); + + void doPushTemporary(const st::InstructionNode& instruction); + void doAssignTemporary(const st::InstructionNode& instruction); + + void doPushBlock(const st::InstructionNode& instruction); + + void doSendUnary(const st::InstructionNode& instruction); + void doSendBinary(st::InstructionNode& instruction); + void doMarkArguments(const st::InstructionNode& instruction); + void doSendMessage(st::InstructionNode& instruction, bool sendToSuper = false); + + void doPrimitive(const st::InstructionNode& instruction); + void doSpecial(st::InstructionNode& instruction); + +private: + void captureContext(st::InstructionNode& instruction, Type& arguments); + InferContext* getMethodContext(); + + void fillLinkerClosures(); + +private: + + class Walker : public st::GraphWalker { + public: + Walker(TypeAnalyzer& analyzer) : analyzer(analyzer) {} + + private: + TVisitResult visitNode(st::ControlNode& node, const TPathNode*) { + if (st::InstructionNode* const instruction = node.cast()) + analyzer.processInstruction(*instruction); + + return vrKeepWalking; + } + + void nodesVisited() { + analyzer.walkComplete(); + } + + private: + TypeAnalyzer& analyzer; + }; + +private: + TypeSystem& m_system; + st::ControlGraph& m_graph; + TContextStack& m_contextStack; + InferContext& m_context; + + st::TauLinker m_tauLinker; + Walker m_walker; + + typedef std::map TSiteMap; + TSiteMap m_siteMap; + + bool m_baseRun; + bool m_literalBranch; + + const Type* m_blockType; +}; + +inline type::Type createArgumentsType(TObjectArray* arguments) { + type::Type result(globals.arrayClass, type::Type::tkArray); + + for (std::size_t i = 0; i < arguments->getSize(); i++) { + TObject* const argument = arguments->getField(i); + TClass* const klass = isSmallInteger(argument) ? globals.smallIntClass : argument->getClass(); + result.pushSubType(type::Type(klass)); + } + + return result; +} + +} // namespace type + +#endif // LLST_INFERENCE_H_INCLUDED diff --git a/include/jit.h b/include/jit.h index d0a0002..ec8e5c0 100644 --- a/include/jit.h +++ b/include/jit.h @@ -35,6 +35,7 @@ #include #include "vm.h" #include "analysis.h" +#include "inference.h" #include @@ -197,6 +198,8 @@ class MethodCompiler { }; struct TJITContext { + bool isBlock; + st::ParsedMethod* parsedMethod; st::ControlGraph* controlGraph; st::InstructionNode* currentNode; @@ -206,6 +209,7 @@ class MethodCompiler { TPhiList pendingPhiNodes; TMethod* originMethod; // Smalltalk method we're currently processing + type::InferContext& inferContext; llvm::Function* function; // LLVM function that is created based on method llvm::IRBuilder<>* builder; // Builder inserts instructions into basic blocks @@ -222,16 +226,35 @@ class MethodCompiler { llvm::Value* contextHolder; llvm::Value* selfHolder; + llvm::Value* temporaries; llvm::Value* getCurrentContext(); llvm::Value* getSelf(); llvm::Value* getMethodClass(); llvm::Value* getLiteral(uint32_t index); - TJITContext(MethodCompiler* compiler, TMethod* method, bool parse = true) - : currentNode(0), originMethod(method), function(0), builder(0), - preamble(0), exceptionLandingPad(0), unwindBlockReturn(0), unwindPhi(0), methodHasBlockReturn(false), - methodAllocatesMemory(true), compiler(compiler), contextHolder(0), selfHolder(0) + TJITContext( + MethodCompiler* compiler, + TMethod* method, + type::InferContext& context, + bool parse = true + ) : + isBlock(false), + currentNode(0), + originMethod(method), + inferContext(context), + function(0), + builder(0), + preamble(0), + exceptionLandingPad(0), + unwindBlockReturn(0), + unwindPhi(0), + methodHasBlockReturn(false), + methodAllocatesMemory(true), + compiler(compiler), + contextHolder(0), + selfHolder(0), + temporaries(0) { if (parse) { parsedMethod = new st::ParsedMethod(method); @@ -252,14 +275,16 @@ class MethodCompiler { TJITBlockContext( MethodCompiler* compiler, st::ParsedMethod* method, - st::ParsedBlock* block - ) - : TJITContext(compiler, 0, false), parsedBlock(block) + st::ParsedBlock* block, + st::ControlGraph* blockGraph, + type::InferContext& context + ) : + TJITContext(compiler, 0, context, false), + parsedBlock(block) { parsedMethod = method; originMethod = parsedMethod->getOrigin(); - controlGraph = new st::ControlGraph(method, block); - controlGraph->buildGraph(); + controlGraph = blockGraph; } ~TJITBlockContext() { @@ -267,6 +292,7 @@ class MethodCompiler { } }; + type::TypeSystem& getTypeSystem() { return m_typeSystem; } private: JITRuntime& m_runtime; @@ -282,6 +308,10 @@ class MethodCompiler { TExceptionAPI m_exceptionAPI; TBaseFunctions m_baseFunctions; +private: + type::TypeSystem m_typeSystem; + +private: llvm::Value* getNodeValue(TJITContext& jit, st::ControlNode* node, llvm::BasicBlock* insertBlock = 0); llvm::Value* getPhiValue(TJITContext& jit, st::PhiNode* phi); void encodePhiIncomings(TJITContext& jit, st::PhiNode* phiNode); @@ -291,7 +321,7 @@ class MethodCompiler { llvm::Value* allocateRoot(TJITContext& jit, llvm::Type* type); llvm::Value* protectPointer(TJITContext& jit, llvm::Value* value); llvm::Value* protectProducerNode(TJITContext& jit, st::ControlNode* node, llvm::Value* value); - bool shouldProtectProducer(st::ControlNode* node); + bool shouldProtectProducer(TJITContext& jit, st::ControlNode* node); bool methodAllocatesMemory(TJITContext& jit); void writePreamble(TJITContext& jit, bool isBlock = false); @@ -308,7 +338,8 @@ class MethodCompiler { void doPushConstant(TJITContext& jit); void doPushBlock(TJITContext& jit); - llvm::Function* compileBlock(TJITContext& jit, const std::string& blockFunctionName, st::ParsedBlock* parsedBlock); + llvm::Function* compileBlock(const std::string& blockFunctionName, st::ParsedBlock* parsedBlock, type::InferContext& blockContext); + llvm::Function* compileInferredBlock(TJITContext& jit); void doAssignTemporary(TJITContext& jit); void doAssignInstance(TJITContext& jit); @@ -316,7 +347,8 @@ class MethodCompiler { void doSendUnary(TJITContext& jit); void doSendBinary(TJITContext& jit); void doSendMessage(TJITContext& jit); - bool doSendMessageToLiteral(TJITContext& jit, st::InstructionNode* receiverNode, TClass* receiverClass = 0); + void doSendGenericMessage(TJITContext& jit); + void doSendInferredMessage(TJITContext& jit, type::InferContext& context); void doSpecial(TJITContext& jit); void doPrimitive(TJITContext& jit); @@ -334,7 +366,7 @@ class MethodCompiler { llvm::BasicBlock* primitiveFailedBB); TObjectAndSize createArray(TJITContext& jit, uint32_t elementsCount); - llvm::Function* createFunction(TMethod* method); + llvm::Function* createFunction(TMethod* method, const std::string& functionName); uint16_t getSkipOffset(st::InstructionNode* branch); @@ -349,11 +381,12 @@ class MethodCompiler { llvm::Function* compileMethod( TMethod* method, + const type::Type& arguments, llvm::Function* methodFunction = 0, llvm::Value** contextHolder = 0 ); - llvm::Function* compileBlock(TBlock* block); + llvm::Function* compileDynamicBlock(TBlock* block); // TStackObject is a pair of entities allocated on a thread stack space // objectSlot is a container for actual object's data @@ -366,19 +399,15 @@ class MethodCompiler { TStackObject allocateStackObject(llvm::IRBuilder<>& builder, uint32_t baseSize, uint32_t fieldsCount); + void insertTrace(TJITContext& jit, const char* message); + void insertTrace(TJITContext& jit, const char* message, llvm::Value* value); + MethodCompiler( JITRuntime& runtime, llvm::Module* JITModule, TRuntimeAPI runtimeApi, TExceptionAPI exceptionApi - ) - : m_runtime(runtime), m_JITModule(JITModule), - m_runtimeAPI(runtimeApi), m_exceptionAPI(exceptionApi), m_callSiteIndex(1) - { - m_baseTypes.initializeFromModule(JITModule); - m_globals.initializeFromModule(JITModule); - m_baseFunctions.initializeFromModule(JITModule); - } + ); }; @@ -398,7 +427,7 @@ extern "C" { TObject* newOrdinaryObject(TClass* klass, uint32_t slotSize); TByteObject* newBinaryObject(TClass* klass, uint32_t dataSize); TReturnValue sendMessage(TContext* callingContext, TSymbol* message, TObjectArray* arguments, TClass* receiverClass, uint32_t callSiteIndex); - TBlock* createBlock(TContext* callingContext, uint8_t argLocation, uint16_t bytePointer); + TBlock* createBlock(TContext* callingContext, uint8_t argLocation, uint16_t bytePointer, uint32_t stackTop); TReturnValue invokeBlock(TBlock* block, TContext* callingContext); void emitBlockReturn(TObject* value, TContext* targetContext); const void* getBlockReturnType(); @@ -437,18 +466,21 @@ class JITRuntime { void sendMessage(TContext* callingContext, TSymbol* message, TObjectArray* arguments, TClass* receiverClass, uint32_t callSiteIndex, TReturnValue& result); void invokeBlock(TBlock* block, TContext* callingContext, TReturnValue& result); - TBlock* createBlock(TContext* callingContext, uint8_t argLocation, uint16_t bytePointer); + TBlock* createBlock(TContext* callingContext, uint8_t argLocation, uint16_t bytePointer, uint32_t stackTop); friend TObject* newOrdinaryObject(TClass* klass, uint32_t slotSize); friend TByteObject* newBinaryObject(TClass* klass, uint32_t dataSize); friend TReturnValue sendMessage(TContext* callingContext, TSymbol* message, TObjectArray* arguments, TClass* receiverClass, uint32_t callSiteIndex); - friend TBlock* createBlock(TContext* callingContext, uint8_t argLocation, uint16_t bytePointer); + friend TBlock* createBlock(TContext* callingContext, uint8_t argLocation, uint16_t bytePointer, uint32_t stackTop); friend TReturnValue invokeBlock(TBlock* block, TContext* callingContext); friend void emitBlockReturn(TObject* value, TContext* targetContext); + static const unsigned int ARG_CACHE_SIZE = 8; struct TFunctionCacheEntry { TMethod* method; + TClass* arguments[ARG_CACHE_SIZE]; + TMethodFunction function; }; @@ -456,11 +488,15 @@ class JITRuntime { { TMethod* containerMethod; uint32_t blockOffset; + TClass* closures[ARG_CACHE_SIZE * 2]; TBlockFunction function; }; static const unsigned int LOOKUP_CACHE_SIZE = 512; + static const unsigned int METHOD_CACHE_ASSOCIATIVITY = 4; + static const unsigned int BLOCK_CACHE_ASSOCIATIVITY = 4; + TFunctionCacheEntry m_functionLookupCache[LOOKUP_CACHE_SIZE]; TBlockFunctionCacheEntry m_blockFunctionLookupCache[LOOKUP_CACHE_SIZE]; uint32_t m_cacheHits; @@ -472,12 +508,23 @@ class JITRuntime { uint32_t m_blockReturnsEmitted; uint32_t m_objectsAllocated; - TMethodFunction lookupFunctionInCache(TMethod* method); - TBlockFunction lookupBlockFunctionInCache(TMethod* containerMethod, uint32_t blockOffset); - void updateFunctionCache(TMethod* method, TMethodFunction function); - void updateBlockFunctionCache(TMethod* containerMethod, uint32_t blockOffset, TBlockFunction function); + TMethodFunction lookupFunctionInCache(TMethod* method, TObjectArray* arguments); + TBlockFunction lookupBlockFunctionInCache(TBlock* block); + void updateFunctionCache(TMethod* method, TMethodFunction function, TObjectArray* arguments); + void updateBlockFunctionCache(TBlock* block, TBlockFunction function); void flushBlockFunctionCache(); + void fillMethodSlot( + TFunctionCacheEntry& entry, + TMethod* method, + TMethodFunction function, + TObjectArray* arguments); + + void fillBlockSlot( + TBlockFunctionCacheEntry& slot, + TBlock* block, + TBlockFunction function); + void initializePassManager(); //The following methods use m_baseTypes. Don't forget to init it before calling these methods diff --git a/include/types.h b/include/types.h index 2a8190d..44720f1 100644 --- a/include/types.h +++ b/include/types.h @@ -70,6 +70,8 @@ struct TInteger { int32_t operator -(int32_t right) const { return getValue() - right; } operator TObject*() const { return reinterpret_cast(m_value); } + void setRawValue(int32_t value) { m_value = value; } + private: int32_t m_value; protected: @@ -235,6 +237,7 @@ struct TSymbol : public TByteObject { // Strings are binary objects that hold raw character bytes. struct TString : public TByteObject { static const char* InstanceClassName() { return "String"; } + std::string toString() const { return std::string(reinterpret_cast(bytes), getSize()); } }; // Chars are intermediate representation of single printable character @@ -286,6 +289,11 @@ typedef TArray TSymbolArray; // will hold temporary objects during the call dispatching and the pointers // to the current executing instruction and the stack top. struct TContext : public TObject { + explicit TContext(TClass* klass) : + TObject(sizeof(TContext) / sizeof(TObject*) - 2, klass, false), + bytePointer(0), + stackTop(0) {} + TMethod* method; TObjectArray* arguments; TObjectArray* temporaries; diff --git a/src/ControlGraph.cpp b/src/ControlGraph.cpp index 522e8fe..5530e2c 100644 --- a/src/ControlGraph.cpp +++ b/src/ControlGraph.cpp @@ -5,6 +5,21 @@ using namespace st; static const bool traces_enabled = false; +ControlGraph::TMetaInfo::TMetaInfo() : + isBlock(false), + hasBlockReturn(false), + hasLiteralBlocks(false), + hasMutatingBlocks(false), + hasLoops(false), + hasBackEdgeTau(false), + usesSelf(false), + usesSuper(false), + readsFields(false), + writesFields(false), + hasPrimitive(false) +{ +} + bool NodeIndexCompare::operator() (const ControlNode* a, const ControlNode* b) const { return a->getIndex() < b->getIndex(); @@ -33,13 +48,66 @@ template<> PushBlockNode* ControlNode::cast() { return static_cast(this); } +template<> const PushBlockNode* ControlNode::cast() const { + return const_cast(this)->cast(); +} + template<> PushBlockNode* ControlGraph::newNode() { - PushBlockNode* node = new PushBlockNode(m_lastNodeIndex++); + PushBlockNode* const node = new PushBlockNode(m_lastNodeIndex++); m_nodes.push_back(node); return static_cast(node); } -TNodeSet PhiNode::getRealValues() { +template<> ClosureTauNode* ControlGraph::newNode() { + ClosureTauNode* const node = new ClosureTauNode(m_lastNodeIndex++); + m_nodes.push_back(node); + return static_cast(node); +} + +template<> ClosureTauNode* ControlNode::cast() { + if (this->getNodeType() != ntTau) + return 0; + + TauNode* const node = static_cast(this); + if (node->getKind() != TauNode::tkClosure) + return 0; + + return static_cast(this); +} + +template<> const ClosureTauNode* ControlNode::cast() const { + return const_cast(this)->cast(); +} + +template<> BranchNode* ControlNode::cast() { + if (this->getNodeType() != ntInstruction) + return 0; + + InstructionNode* const node = static_cast(this); + if (node->getInstruction().getOpcode() != opcode::doSpecial) + return 0; + + switch (node->getInstruction().getArgument()) { + case special::branch: + case special::branchIfTrue: + case special::branchIfFalse: + return static_cast(this); + } + + return 0; +} + +template<> const BranchNode* ControlNode::cast() const { + return const_cast(this)->cast(); +} + +template<> BranchNode* ControlGraph::newNode() { + BranchNode* const node = new BranchNode(m_lastNodeIndex++); + m_nodes.push_back(node); + return static_cast(node); +} + +TNodeSet PhiNode::getRealValues() const { TNodeSet values; for (std::size_t i = 0; i < m_incomingList.size(); i++) { @@ -101,10 +169,13 @@ class GraphConstructor : public InstructionVisitor { InstructionNode* GraphConstructor::createNode(const TSmalltalkInstruction& instruction) { + if (instruction.isBranch()) + return m_graph->newNode(); + if (instruction.getOpcode() == opcode::pushBlock) return m_graph->newNode(); - else - return m_graph->newNode(); + + return m_graph->newNode(); } void GraphConstructor::processNode(InstructionNode* node) @@ -115,15 +186,34 @@ void GraphConstructor::processNode(InstructionNode* node) m_currentDomain->setEntryPoint(node); switch (instruction.getOpcode()) { - case opcode::pushConstant: - case opcode::pushLiteral: case opcode::pushArgument: - case opcode::pushTemporary: // TODO Link with tau node + if (instruction.getArgument() == 0) + m_graph->getMeta().usesSelf = true; + ControlGraph::TMetaInfo::insertIndex(instruction.getArgument(), m_graph->getMeta().readsArguments); + m_currentDomain->pushValue(node); + break; + case opcode::pushInstance: + m_graph->getMeta().readsFields = true; + m_currentDomain->pushValue(node); + break; + + case opcode::pushTemporary: + ControlGraph::TMetaInfo::insertIndex(instruction.getArgument(), m_graph->getMeta().readsTemporaries); + case opcode::pushConstant: + case opcode::pushLiteral: m_currentDomain->pushValue(node); break; case opcode::pushBlock: { + // FIXME m_graph->getMeta().hasLiteralBlocks = true; + // + // NOTE Technically, we may set the meta here, + // but we may get false-positive value + // due to a known bug in the imageBuilder + // which leaves dead PushBlock instructions + // when inlining key selectors. + const uint16_t blockEndOffset = node->getInstruction().getExtra(); ParsedMethod* const parsedMethod = m_graph->getParsedMethod(); ParsedBlock* const parsedBlock = parsedMethod->getParsedBlockByEndOffset(blockEndOffset); @@ -132,8 +222,13 @@ void GraphConstructor::processNode(InstructionNode* node) m_currentDomain->pushValue(node); } break; - case opcode::assignTemporary: // TODO Link with tau node + case opcode::assignTemporary: + ControlGraph::TMetaInfo::insertIndex(instruction.getArgument(), m_graph->getMeta().writesTemporaries); + m_currentDomain->requestArgument(0, node, true); + break; + case opcode::assignInstance: + m_graph->getMeta().writesFields = true; m_currentDomain->requestArgument(0, node, true); break; @@ -160,6 +255,7 @@ void GraphConstructor::processNode(InstructionNode* node) break; case opcode::doPrimitive: + m_graph->getMeta().hasPrimitive = true; processPrimitives(node); m_currentDomain->pushValue(node); break; @@ -174,8 +270,9 @@ void GraphConstructor::processSpecials(InstructionNode* node) const TSmalltalkInstruction& instruction = node->getInstruction(); switch (instruction.getArgument()) { - case special::stackReturn: case special::blockReturn: + m_graph->getMeta().hasBlockReturn = true; + case special::stackReturn: m_currentDomain->requestArgument(0, node); case special::selfReturn: @@ -184,6 +281,7 @@ void GraphConstructor::processSpecials(InstructionNode* node) break; case special::sendToSuper: + m_graph->getMeta().usesSuper = true; m_currentDomain->requestArgument(0, node); m_currentDomain->pushValue(node); break; @@ -320,22 +418,30 @@ void GraphLinker::processBranching() const BasicBlock::TBasicBlockSet& referers = m_currentDomain->getBasicBlock()->getReferers(); BasicBlock::TBasicBlockSet::iterator iReferer = referers.begin(); for (; iReferer != referers.end(); ++iReferer) { - ControlDomain* const refererDomain = m_graph->getDomainFor(*iReferer); - InstructionNode* const terminator = refererDomain->getTerminator(); - assert(terminator && terminator->getInstruction().isBranch()); + ControlDomain* const refererDomain = getGraph().getDomainFor(*iReferer); + BranchNode* const branch = refererDomain->getTerminator()->cast(); + assert(branch); + + if (entryPoint->getDomain()->getBasicBlock()->getOffset() == branch->getInstruction().getExtra()) + branch->setTargetNode(entryPoint); + else + branch->setSkipNode(entryPoint); if (traces_enabled) - std::printf("GraphLinker::processNode : linking nodes of referring graphs %.2u and %.2u\n", terminator->getIndex(), entryPoint->getIndex()); + std::printf("GraphLinker::processNode : linking nodes of referring graphs %.2u and %.2u\n", branch->getIndex(), entryPoint->getIndex()); - terminator->addEdge(entryPoint); + branch->addEdge(entryPoint); } } void GraphLinker::processArgumentRequests() { const ControlDomain::TRequestList& requestList = m_currentDomain->getRequestedArguments(); - for (std::size_t index = 0; index < requestList.size(); index++) - processRequest(m_currentDomain, index, requestList[index]); + for (std::size_t index = 0, argIndex = 0; index < requestList.size(); index++) { + processRequest(m_currentDomain, argIndex, requestList[index]); + if (!requestList[index].keep) + argIndex++; + } } void GraphLinker::processRequest(ControlDomain* domain, std::size_t argumentIndex, const ControlDomain::TArgumentRequest& request) @@ -368,7 +474,7 @@ void GraphLinker::mergePhi(PhiNode* source, PhiNode* target) } // Deleting source node because it is no longer used - m_graph->eraseNode(source); + getGraph().eraseNode(source); } ControlNode* GraphLinker::optimizePhi(PhiNode* phi) @@ -399,11 +505,12 @@ ControlNode* GraphLinker::optimizePhi(PhiNode* phi) // This is the real value that should be returned ControlNode* const value = *incomingValues.begin(); + assert(value); // Unlink and erase phi value->removeConsumer(phi); value->removeEdge(phi); - m_graph->eraseNode(phi); + getGraph().eraseNode(phi); return value; } @@ -418,14 +525,14 @@ ControlNode* GraphLinker::getRequestedNode(ControlDomain* domain, std::size_t ar ControlNode* result = 0; if (!singleReferer) { - PhiNode* const phi = m_graph->newNode(); + PhiNode* const phi = getGraph().newNode(); phi->setDomain(domain); result = phi; } BasicBlock::TBasicBlockSet::iterator iBlock = refererBlocks.begin(); for (; iBlock != refererBlocks.end(); ++iBlock) { - ControlDomain* const refererDomain = m_graph->getDomainFor(* iBlock); + ControlDomain* const refererDomain = getGraph().getDomainFor(* iBlock); const TNodeList& refererStack = refererDomain->getLocalStack(); const std::size_t refererStackSize = refererStack.size(); @@ -474,48 +581,26 @@ class GraphOptimizer : public PlainNodeVisitor { public: GraphOptimizer(ControlGraph* graph) : PlainNodeVisitor(graph) {} - virtual bool visitNode(ControlNode& node) { - // If node pushes value on the stack but this value is not consumed - // by another node, or the only consumer is a popTop instruction - // then we may remove such node (or a node pair) - - if (InstructionNode* const instruction = node.cast()) { - const TSmalltalkInstruction& nodeInstruction = instruction->getInstruction(); - if (!nodeInstruction.isTrivial() || !nodeInstruction.isValueProvider()) - return true; + bool graphAltered() const { return !m_nodesToRemove.empty(); } - const TNodeSet& consumers = instruction->getConsumers(); - if (consumers.empty()) { - if (traces_enabled) - std::printf("GraphOptimizer::visitNode : node %u is not consumed and may be removed\n", instruction->getIndex()); - - m_nodesToRemove.push_back(instruction); - } else if (consumers.size() == 1) { - if (InstructionNode* const consumer = (*consumers.begin())->cast()) { - const TSmalltalkInstruction& consumerInstruction = consumer->getInstruction(); - if (consumerInstruction == TSmalltalkInstruction(opcode::doSpecial, special::popTop)) { - if (traces_enabled) - std::printf("GraphOptimizer::visitNode : node %u is consumed only by popTop %u and may be removed\n", - instruction->getIndex(), - consumer->getIndex() - ); - - m_nodesToRemove.push_back(consumer); - m_nodesToRemove.push_back(instruction); - } - } - } - } +private: + virtual bool visitNode(ControlNode& node) { +// if (BranchNode* const branch = node.cast()) +// checkBranch(*branch); +// else + if (InstructionNode* const instruction = node.cast()) + checkInstruction(*instruction); return true; } virtual void nodesVisited() { // Removing nodes that were optimized out - TNodeList::iterator iNode = m_nodesToRemove.begin(); + TNodeSet::const_iterator iNode = m_nodesToRemove.begin(); for (; iNode != m_nodesToRemove.end(); ++iNode) { assert((*iNode)->getNodeType() == ControlNode::ntInstruction || (*iNode)->getNodeType() == ControlNode::ntPhi); + if (InstructionNode* const instruction = (*iNode)->cast()) removeInstruction(instruction); else if (PhiNode* const phi = (*iNode)->cast()) @@ -524,6 +609,70 @@ class GraphOptimizer : public PlainNodeVisitor { } private: + void checkBranch(const BranchNode& branch) { + // If branch is targets to an unconditional branch, latter may be removed + + if (BranchNode* const target_branch = branch.getTargetNode()->cast()) { + if (! target_branch->getSkipNode()) { + if (traces_enabled) + std::printf("GraphOptimizer::visitNode : node %u is branch to unconditional branch %u and latter may be removed\n", + branch.getIndex(), target_branch->getIndex()); + + m_nodesToRemove.insert(target_branch); + return; + } + } + + if (!branch.getSkipNode()) + return; + + if (BranchNode* const target_branch = branch.getSkipNode()->cast()) { + if (! target_branch->getSkipNode()) { + if (traces_enabled) + std::printf("GraphOptimizer::visitNode : node %u is branch to unconditional branch %u and latter may be removed\n", + branch.getIndex(), target_branch->getIndex()); + + m_nodesToRemove.insert(target_branch); + return; + } + } + } + + void checkInstruction(InstructionNode& instruction) { + // If node pushes value on the stack but this value is not consumed + // by another node, or the only consumer is a popTop instruction + // then we may remove such node (or a node pair) + + const TSmalltalkInstruction& nodeInstruction = instruction.getInstruction(); + if (!nodeInstruction.isTrivial() || !nodeInstruction.isValueProvider()) { + if (! instruction.cast()) + return; + } + + const TNodeSet& consumers = instruction.getConsumers(); + if (consumers.empty()) { + if (traces_enabled) + std::printf("GraphOptimizer::visitNode : node %u is not consumed and may be removed\n", instruction.getIndex()); + + m_nodesToRemove.insert(&instruction); + } else if (consumers.size() == 1) { + if (InstructionNode* const consumer = (*consumers.begin())->cast()) { + const TSmalltalkInstruction& consumerInstruction = consumer->getInstruction(); + if (consumerInstruction == TSmalltalkInstruction(opcode::doSpecial, special::popTop)) { + if (traces_enabled) + std::printf("GraphOptimizer::visitNode : node %u is consumed only by popTop %u and may be removed\n", + instruction.getIndex(), + consumer->getIndex() + ); + + // TODO Remove phi incoming and probably remove phi node along with it's consumer + m_nodesToRemove.insert(consumer); + m_nodesToRemove.insert(&instruction); + } + } + } + } + void removePhi(PhiNode* phi) { assert(phi->getInEdges().size() == 1); @@ -562,8 +711,10 @@ class GraphOptimizer : public PlainNodeVisitor { if (domain->getEntryPoint() == node) domain->setEntryPoint(nextNode->cast()); + // TODO Phi node + // Fixing incoming edges by remapping them to the next node - TNodeSet::iterator iNode = node->getInEdges().begin(); + TNodeSet::const_iterator iNode = node->getInEdges().begin(); while (iNode != node->getInEdges().end()) { ControlNode* const sourceNode = *iNode++; @@ -574,6 +725,19 @@ class GraphOptimizer : public PlainNodeVisitor { nextNode->getIndex() ); + if (BranchNode* const branch = sourceNode->cast()) { + if (traces_enabled) + std::printf("Patching branch info %.2u -> %.2u\n", + sourceNode->getIndex(), + nextNode->getIndex() + ); + + if (branch->getTargetNode() == node) + branch->setTargetNode(nextNode); + else + branch->setSkipNode(nextNode); + } + sourceNode->removeEdge(node); sourceNode->addEdge(nextNode); } @@ -599,7 +763,7 @@ class GraphOptimizer : public PlainNodeVisitor { } private: - TNodeList m_nodesToRemove; + TNodeSet m_nodesToRemove; }; void ControlGraph::buildGraph() @@ -608,8 +772,10 @@ void ControlGraph::buildGraph() std::printf("Phase 1. Constructing control graph\n"); // Iterating through basic blocks of parsed method and constructing node domains - GraphConstructor constructor(this); - constructor.run(); + { + GraphConstructor constructor(this); + constructor.run(); + } if (traces_enabled) std::printf("Phase 2. Linking control graph\n"); @@ -618,13 +784,54 @@ void ControlGraph::buildGraph() // They're linked using phi nodes or a direct link if possible. // Also branching edges are added so graph remains linked even if // no stack relations exist. - GraphLinker linker(this); - linker.run(); + { + GraphLinker linker(this); + linker.run(); + } if (traces_enabled) std::printf("Phase 3. Optimizing control graph\n"); // Optimizing graph by removing stalled nodes and merging linear branch sequences - GraphOptimizer optimizer(this); - optimizer.run(); + { + GraphOptimizer optimizer(this); + optimizer.run(); + } + + if (traces_enabled) + std::printf("Phase 4. Linking PushTemporary and AssignTemporary nodes\n"); } + +void ControlGraph::eraseTauNodes() { + if (isEmpty()) + return; + + for (ControlGraph::reverse_iterator iNode = nodes_rbegin(); iNode != nodes_rend(); ) { + if (TauNode* const tau = (*iNode)->cast()) { + if (traces_enabled) + std::printf("Erasing tau %.2u\n", tau->getIndex()); + + const TauNode::TIncomingMap& incomings = tau->getIncomingMap(); + TauNode::TIncomingMap::const_iterator iIncoming = incomings.begin(); + for (; iIncoming != incomings.end(); ++iIncoming) { + if (InstructionNode* const instruction = iIncoming->first->cast()) + instruction->setTauNode(0); + + iIncoming->first->removeConsumer(tau); + } + + const TNodeSet& consumers = tau->getConsumers(); + for (TNodeSet::const_iterator iConsumer = consumers.begin(); iConsumer != consumers.end(); ++iConsumer) { + if (InstructionNode* const instruction = (*iConsumer)->cast()) { + assert(instruction->getTauNode() == tau); + instruction->setTauNode(0); + } + } + + ++iNode; + eraseNode(tau); + } else { + ++iNode; + } + } +} \ No newline at end of file diff --git a/src/ControlGraphVisualizer.cpp b/src/ControlGraphVisualizer.cpp index 6af5702..0951fb6 100644 --- a/src/ControlGraphVisualizer.cpp +++ b/src/ControlGraphVisualizer.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -32,6 +33,25 @@ std::string gnu_getcwd() { } } +void gnu_mkdir(const std::string& path) { + int status = mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + if (status != 0) { + std::stringstream ss; + ss << "Cannot create '" << path << "'"; + throw std::ios_base::failure( ss.str() ); + } +} + +bool gnu_dir_exists(const std::string& path) { + struct stat info; + int status = stat(path.c_str(), &info); + if (status != 0) + return false; + if (info.st_mode & S_IFDIR) + return true; + return false; +} + std::string escape_path(const std::string& path) { if (path.empty()) return ""; @@ -55,6 +75,9 @@ std::string escape_path(const std::string& path) { ControlGraphVisualizer::ControlGraphVisualizer(st::ControlGraph* graph, const std::string& fileName, const std::string& directory /*= "."*/) : st::PlainNodeVisitor(graph) { + if (!gnu_dir_exists(directory)) { + gnu_mkdir(directory); + } std::string fullpath = directory + "/" + escape_path(fileName) + ".dot"; m_stream.open(fullpath.c_str(), std::ios::out | std::ios::trunc); if (m_stream.fail()) { @@ -74,17 +97,13 @@ bool ControlGraphVisualizer::visitDomain(st::ControlDomain& /*domain*/) { } std::string edgeStyle(st::ControlNode* from, st::ControlNode* to) { - const st::InstructionNode* const fromInstruction = from->cast(); - const st::InstructionNode* const toInstruction = to->cast(); + const st::InstructionNode* const toInstruction = to->cast(); if (from->getNodeType() == st::ControlNode::ntPhi && to->getNodeType() == st::ControlNode::ntPhi) return "[style=invis color=red constraint=false]"; - if (fromInstruction && fromInstruction->getInstruction().isBranch()) - return "[color=\"grey\" style=\"dashed\"]"; - if (toInstruction && toInstruction->getArgumentsCount() == 0) - return "[color=\"black\" style=\"dashed\" ]"; + return "[weight=100 color=\"black\" style=\"dashed\" ]"; return ""; } @@ -96,31 +115,45 @@ bool ControlGraphVisualizer::visitNode(st::ControlNode& node) { // Processing incoming edges st::TNodeSet::iterator iEdge = inEdges.begin(); for (; iEdge != inEdges.end(); ++iEdge) { - if (isNodeProcessed(*iEdge)) + if (isNodeProcessed(*iEdge) || (*iEdge)->getNodeType() == st::ControlNode::ntPhi) continue; + if (const st::InstructionNode* const instruction = (*iEdge)->cast()) { + // Branch edges are handled separately + if (instruction->getInstruction().isBranch()) + continue; + } + m_stream << "\t\t" << (*iEdge)->getIndex() << " -> " << node.getIndex() << edgeStyle(*iEdge, &node) << ";\n"; } - // Processing outgoing edges - iEdge = outEdges.begin(); - for (; iEdge != outEdges.end(); ++iEdge) { - if (isNodeProcessed(*iEdge)) - continue; - - m_stream << "\t\t" << node.getIndex() << " -> " << (*iEdge)->getIndex() << edgeStyle(&node, *iEdge) << ";\n"; - } + bool outEdgesProcessed = false; // Processing argument edges if (const st::InstructionNode* const instruction = node.cast()) { const std::size_t argsCount = instruction->getArgumentsCount(); for (std::size_t index = 0; index < argsCount; index++) { - m_stream << "\t\t" << node.getIndex() << " -> " << instruction->getArgument(index)->getIndex() << " ["; + m_stream << "\t\t" << instruction->getArgument(index)->getIndex() << " -> " << node.getIndex() << " ["; if (argsCount > 1) m_stream << "label=" << index; - m_stream << " labelfloat=true color=\"blue\" fontcolor=\"blue\" style=\"dashed\" constraint=false];\n"; + m_stream << "dir=back weight=8 labelfloat=true color=\"blue\" fontcolor=\"blue\" style=\"dashed\" constraint=true];\n"; + } + + if (const st::BranchNode* const branch = node.cast()) { + m_stream + << "\t\t" << node.getIndex() << " -> " << branch->getTargetNode()->getIndex() << " [" + << (branch->getSkipNode() ? " label=target " : "") + << "weight=20 labelfloat=true color=\"grey\" fontcolor=\"grey\" style=\"dashed\"];\n"; + + if (branch->getSkipNode()) { + m_stream + << "\t\t" << node.getIndex() << " -> " << branch->getSkipNode()->getIndex() << " [" + << "weight=20 label=skip labelfloat=true color=\"grey\" fontcolor=\"grey\" style=\"dashed\"];\n"; + } + + outEdgesProcessed = true; } } else if (const st::PhiNode* const phi = node.cast()) { @@ -137,6 +170,61 @@ bool ControlGraphVisualizer::visitNode(st::ControlNode& node) { m_stream << "\t\t" << incoming.domain->getTerminator()->getIndex() << " -> " << phi->getIndex() << " [" << "style=\"invis\" constraint=true ];\n"; } + } else if (const st::TauNode* const tau = node.cast()) { + + for (st::TauNode::TIncomingMap::const_iterator iNode = tau->getIncomingMap().begin(); + iNode != tau->getIncomingMap().end(); + ++iNode) + { + if (tau->getKind() == st::TauNode::tkProvider) { + m_stream << "\t\t" << iNode->first->getIndex() << " -> " << tau->getIndex() << " [" + << "weight=15 dir=back labelfloat=true color=\"red\" fontcolor=\"red\" style=\"dashed\" constraint=true ];\n"; +// } else if (tau->getKind() == st::TauNode::tkClosure) { +// m_stream << "\t\t" << iNode->first->getIndex() << " -> " << tau->getIndex() << " [" +// << "weight=15 dir=back labelfloat=true color=\"orange\" fontcolor=\"orange\" style=\"dashed\" constraint=true ];\n"; + } else { + const bool byBackEdge = iNode->second; + m_stream << "\t\t" << iNode->first->getIndex() << " -> " << tau->getIndex() << " [" + << "weight=5 dir=back labelfloat=true color=\"" + << (byBackEdge ? "blue" : "grey") + << "\" fontcolor=\"green\" style=\"dotted\" constraint=true ];\n"; + } + } + + for (st::TNodeSet::iterator iNode = tau->getConsumers().begin(); iNode != tau->getConsumers().end(); ++iNode) { + if ((*iNode)->getNodeType() == st::ControlNode::ntTau) + continue; + + if (tau->getKind() == st::TauNode::tkClosure) { + if (static_cast(tau)->getOrigin() == *iNode) { + m_stream << "\t\t"; + + if (tau->getIncomingMap().empty()) + m_stream << (*iNode)->getIndex() << " -> " << tau->getIndex(); + else + m_stream << tau->getIndex() << " -> " << (*iNode)->getIndex(); + + m_stream << " [ weight=25 dir=back labelfloat=true color=\"orange\" fontcolor=\"orange\" style=\"dashed\" constraint=true ];\n"; + + continue; + } + } + + m_stream + << "\t\t" << tau->getIndex() << " -> " << (*iNode)->getIndex() << " [" + << "weight=15 dir=back labelfloat=true color=\"green\" fontcolor=\"green\" style=\"dashed\" constraint=true ];\n"; + } + } + + // Processing outgoing edges in generic way + if (!outEdgesProcessed) { + iEdge = outEdges.begin(); + for (; iEdge != outEdges.end(); ++iEdge) { + if (isNodeProcessed(*iEdge)) + continue; + + m_stream << "\t\t" << node.getIndex() << " -> " << (*iEdge)->getIndex() << edgeStyle(&node, *iEdge) << ";\n"; + } } markNode(&node); @@ -156,13 +244,24 @@ void ControlGraphVisualizer::markNode(st::ControlNode* node) { switch (node->getNodeType()) { case st::ControlNode::ntPhi: //label = "Phi "; - color = "grey"; + color = "blue"; shape = "oval"; break; case st::ControlNode::ntTau: label = "Tau "; - color = "green"; + + switch (node->cast()->getKind()) { + case st::TauNode::tkProvider: color = "red"; break; + case st::TauNode::tkClosure: color = "orange"; break; + + case st::TauNode::tkAggregator: + default: + color = "green"; break; + break; + } + + shape = "oval"; break; case st::ControlNode::ntInstruction: { @@ -202,7 +301,7 @@ void ControlGraphVisualizer::markNode(st::ControlNode* node) { ; } - if (node->getNodeType() == st::ControlNode::ntPhi) + if (node->getNodeType() == st::ControlNode::ntPhi || node->getNodeType() == st::ControlNode::ntTau) m_stream << "\t\t" << node->getIndex() << " [label=\"" << node->getIndex() << "\" color=\"" << color << "\"];\n"; else m_stream << "\t\t" << node->getIndex() << " [shape=\"" << shape << "\" label=\"" << (node->getDomain() ? node->getDomain()->getBasicBlock()->getOffset() : 666) << "." << node->getIndex() << " : " << label << "\" color=\"" << color << "\"];\n"; diff --git a/src/JITRuntime.cpp b/src/JITRuntime.cpp index 5c92f4f..a47ef20 100644 --- a/src/JITRuntime.cpp +++ b/src/JITRuntime.cpp @@ -68,25 +68,23 @@ static bool compareByHitCount(const JITRuntime::THotMethod* m1, const JITRuntime void JITRuntime::printStat() { - float hitRatio = 100.0 * m_cacheHits / (m_cacheHits + m_cacheMisses); - float blockHitRatio = 100.0 * m_blockCacheHits / (m_blockCacheHits + m_blockCacheMisses); + float messagehitRatio = 100.0 * m_cacheHits / (m_cacheHits + m_cacheMisses); + float blockHitRatio = 100.0 * m_blockCacheHits / (m_blockCacheHits + m_blockCacheMisses); std::printf( "JIT Runtime stat:\n" - "\tMessages dispatched: %12d\n" - "\tObjects allocated: %12d\n" - "\tBlocks invoked: %12d\n" - "\tBlockReturn emitted: %12d\n" - "\tBlock cache hits: %12d misses %10d ratio %6.2f %%\n" - "\tMessage cache hits: %12d misses %10d ratio %6.2f %%\n", - - m_messagesDispatched, - m_objectsAllocated, - m_blocksInvoked, m_blockReturnsEmitted, - m_blockCacheHits, m_blockCacheMisses, blockHitRatio, - m_cacheHits, m_cacheMisses, hitRatio + "\tDynamic messages: %12d total, %12d hits, %10d misses, hit ratio %6.2f %%\n" + "\tDynamic blocks: %12d total, %12d hits, %10d misses, hit ratio %6.2f %%\n" + "\tObjects allocated: %12d\n", + + m_messagesDispatched, m_cacheHits, m_cacheMisses, messagehitRatio, + m_blocksInvoked, m_blockCacheHits, m_blockCacheMisses, blockHitRatio, + m_objectsAllocated ); + m_methodCompiler->getTypeSystem().dumpAllContexts(); + m_methodCompiler->getTypeSystem().drawCallGraph(); + std::vector hotMethods; for (THotMethodsMap::iterator iMethod = m_hotMethods.begin(); iMethod != m_hotMethods.end(); ++iMethod) @@ -211,7 +209,7 @@ void JITRuntime::flushBlockFunctionCache() std::memset(&m_blockFunctionLookupCache, 0, sizeof(m_blockFunctionLookupCache)); } -TBlock* JITRuntime::createBlock(TContext* callingContext, uint8_t argLocation, uint16_t bytePointer) +TBlock* JITRuntime::createBlock(TContext* callingContext, uint8_t argLocation, uint16_t bytePointer, uint32_t stackTop) { hptr previousContext = m_softVM->newPointer(callingContext); @@ -224,6 +222,10 @@ TBlock* JITRuntime::createBlock(TContext* callingContext, uint8_t argLocation, u newBlock->arguments = previousContext->arguments; newBlock->temporaries = previousContext->temporaries; + // NOTE This field is used by JIT VM to aid specialization lookup + // See MethodCompiler::getClosureMask() and lookupBlockFunctionInCache() + newBlock->stackTop.setRawValue(stackTop); + // Assigning creatingContext depending on the hierarchy // Nested blocks inherit the outer creating context if (previousContext->getClass() == globals.blockClass) @@ -234,53 +236,337 @@ TBlock* JITRuntime::createBlock(TContext* callingContext, uint8_t argLocation, u return newBlock; } -JITRuntime::TMethodFunction JITRuntime::lookupFunctionInCache(TMethod* method) +JITRuntime::TMethodFunction JITRuntime::lookupFunctionInCache(TMethod* method, TObjectArray* arguments) { - uint32_t hash = reinterpret_cast(method) ^ reinterpret_cast(method->name); // ^ 0xDEADBEEF; - TFunctionCacheEntry& entry = m_functionLookupCache[hash % LOOKUP_CACHE_SIZE]; - - if (entry.method == method) { - m_cacheHits++; - return entry.function; - } else { + if (!arguments || arguments->getSize() > ARG_CACHE_SIZE) { + // Current method has too many arguments that do not fit into the cache. + // It will never be cached, hence no need to search. m_cacheMisses++; return 0; } + + const uint32_t hash = (reinterpret_cast(method) << 2) + arguments->getSize(); + + // All method specializations share the same hash. If cache would be 1-way associative + // then constant cache rotation will occur as several specializations will compete + // against the single cache slot. It order to prevent this we use the n-way cache. + + // Outer loop iterates over the associated slots + for (std::size_t slotIndex = 0; slotIndex < METHOD_CACHE_ASSOCIATIVITY; slotIndex++) { + TFunctionCacheEntry& slot = m_functionLookupCache[(hash + slotIndex) % LOOKUP_CACHE_SIZE]; + + if (slot.method != method) + continue; + + bool match = true; + + // Checking that current entry matches the actual argument types + for (std::size_t argIndex = 0; argIndex < arguments->getSize(); argIndex++) { + TClass* const cachedClass = slot.arguments[argIndex]; + TObject* const field = arguments->getField(argIndex); + TClass* const currentClass = isSmallInteger(field) ? globals.smallIntClass : field->getClass(); + + if (currentClass != cachedClass) { + // Current entry is from another specialization + match = false; + break; + } + } + + if (match) { + // Gotcha! We've just found an entry in the cache + // that has the same method and all passed arguments + // exactly match the stored types in the cache entry. + + // We've found the specialization that may be used. + m_cacheHits++; + return slot.function; + } + } + + // If all associated slots are visited, + // but no matching entry is found then + // it is definitely a cache miss. + + // Sad but true + m_cacheMisses++; + return 0; } -JITRuntime::TBlockFunction JITRuntime::lookupBlockFunctionInCache(TMethod* containerMethod, uint32_t blockOffset) +void JITRuntime::fillMethodSlot( + JITRuntime::TFunctionCacheEntry& slot, + TMethod* method, + JITRuntime::TMethodFunction function, + TObjectArray* arguments) { - uint32_t hash = reinterpret_cast(containerMethod) ^ blockOffset; - TBlockFunctionCacheEntry& entry = m_blockFunctionLookupCache[hash % LOOKUP_CACHE_SIZE]; + slot.method = method; + slot.function = function; - if (entry.containerMethod == containerMethod && entry.blockOffset == blockOffset) { - m_blockCacheHits++; - return entry.function; - } else { - m_blockCacheMisses++; - return 0; + const std::size_t argSize = arguments->getSize(); + for (std::size_t i = 0; i < argSize; i++) { + TObject* const field = arguments->getField(i); + TClass* const currentClass = isSmallInteger(field) ? globals.smallIntClass : field->getClass(); + + slot.arguments[i] = currentClass; } + + // Class list should be zero terminated or filled completely + if (argSize < JITRuntime::ARG_CACHE_SIZE) + slot.arguments[argSize + 1] = 0; } -void JITRuntime::updateFunctionCache(TMethod* method, TMethodFunction function) +void JITRuntime::updateFunctionCache(TMethod* method, TMethodFunction function, TObjectArray* arguments) { - uint32_t hash = reinterpret_cast(method) ^ reinterpret_cast(method->name); // ^ 0xDEADBEEF; - TFunctionCacheEntry& entry = m_functionLookupCache[hash % LOOKUP_CACHE_SIZE]; + // Check if we may cache the method at all. + // Some methods may have too many arguments + // that will not fit into the cache. + if (!arguments || arguments->getSize() > ARG_CACHE_SIZE) + return; - entry.method = method; - entry.function = function; + const uint32_t hash = (reinterpret_cast(method) << 2) + arguments->getSize(); + + // All method specializations share the same hash. If we use 1-way associative cache + // then constant entry rotation will occur, as several specializations will compete + // for the single cache slot. It order to prevent this we use the n-way associative cache. + + // Outer loop iterates over the associated slots + for (std::size_t slotIndex = 0; slotIndex < METHOD_CACHE_ASSOCIATIVITY; slotIndex++) { + TFunctionCacheEntry& slot = m_functionLookupCache[(hash + slotIndex) % LOOKUP_CACHE_SIZE]; + + // We need to find a suitable slot preserving other friendly specializations if possible. + // If we'll find a slot occupied by a friendly specialization, then we'll try again. + if (slot.method == method) + continue; + + // We have found a slot that may be used + fillMethodSlot(slot, method, function, arguments); + return; + } + + // It seem that all slots are occupied by the friendly specializations. + // We need to chose the slot within bounds and overwrite it by our data. + + // Bit shift is required to eliminate pointer alignment or it would not work. + const uint32_t slotIndex = (reinterpret_cast(function) >> 4) % METHOD_CACHE_ASSOCIATIVITY; + + TFunctionCacheEntry& slot = m_functionLookupCache[(hash + slotIndex) % LOOKUP_CACHE_SIZE]; + fillMethodSlot(slot, method, function, arguments); } -void JITRuntime::updateBlockFunctionCache(TMethod* containerMethod, uint32_t blockOffset, TBlockFunction function) +JITRuntime::TBlockFunction JITRuntime::lookupBlockFunctionInCache(TBlock* block) { - uint32_t hash = reinterpret_cast(containerMethod) ^ blockOffset; - TBlockFunctionCacheEntry& entry = m_blockFunctionLookupCache[hash % LOOKUP_CACHE_SIZE]; + // If closure mask is not defined then we could not perform fast lookup. + // This happens either when block came from the SoftVM or because block + // accesses too many temporaries and/or arguments. + if (! (block->stackTop.rawValue() & 1)) + return 0; + + const uint32_t blockOffset = block->blockBytePointer; + TMethod* const containerMethod = block->method; + + // Closure mask is a bit mask representing indices of the temporaries (T) + // and arguments (A) that are accessed from within a block. + // Currently only 17 bits are defined: xxxxxxxxxxxxxxx|AAAAAAAA|TTTTTTTT|1 + + // The least significant bit is set to 1 to indicate that it is not an object pointer. + // It is not used in lookup logic, so we shift it out to make life easier: + const uint32_t closureMask = static_cast(block->stackTop.rawValue()) >> 1; + + // All block specializations share the same hash. If cache would be 1-way associative + // then constant cache rotation will occur as several specializations will compete + // against the single cache slot. It order to prevent this we use the n-way cache. + + const uint32_t hash = (reinterpret_cast(containerMethod) << 16) ^ (blockOffset << 8) ^ closureMask; + + // Outer loop iterates over the associated slots + for (std::size_t slotIndex = 0; slotIndex < METHOD_CACHE_ASSOCIATIVITY; slotIndex++) { + TBlockFunctionCacheEntry& slot = m_blockFunctionLookupCache[(hash + slotIndex) % LOOKUP_CACHE_SIZE]; + + // Quick check that discards most of the entries + if (slot.containerMethod != containerMethod || slot.blockOffset != blockOffset /*|| slot.closureMask != closureMask*/) + continue; // check the next slot + + if (! closureMask) { + // If no closure mask is defined then + // it is already a matching entry + + m_blockCacheHits++; + return slot.function; + } + + bool match = true; + + // If closure mask is defined, it means that block accesses temporaries and/or arguments + // In order to check whether current cached value match we check only the entries that + // correspond to a set bit. Others must be ignored because their value is undefined. + + // Masking out non-interesting bits, only temporaries will remain + if (uint32_t tempBits = closureMask & 0xFF) { + TObjectArray& temporaries = *block->temporaries; + + // Shift bits one by one and check for bits. + // Break if no more non-zero bits remain. + + for (std::size_t index = 0; tempBits; index++, tempBits >>= 1) { + // Check whether temporary corresponding to the current index is used by the block + const bool tempIsUsed = tempBits & 1; + + if (! tempIsUsed) + continue; // check the next temp + + TClass* const cachedClass = slot.closures[index]; + TObject* const field = temporaries[index]; + TClass* const currentClass = isSmallInteger(field) ? globals.smallIntClass : field->getClass(); + + if (currentClass != cachedClass) { + match = false; + break; + } + } + } + + // Quick check for early return + if (! match) + continue; // check the next slot + + // Shifting and masking out non-interesting bits, only arguments will remain + if (uint32_t argBits = (closureMask >> 8) & 0xFF) { + TObjectArray& arguments = *block->arguments; + + for (std::size_t index = 0; argBits; index++, argBits >>= 1) { + // Check whether temporary corresponding to the current index is used by the block + const bool argIsUsed = argBits & 1; - entry.containerMethod = containerMethod; - entry.blockOffset = blockOffset; - entry.function = function; + if (! argIsUsed) + continue; // check the next arg + + TClass* const cachedClass = slot.closures[index + 8]; + TObject* const field = arguments[index]; + TClass* const currentClass = isSmallInteger(field) ? globals.smallIntClass : field->getClass(); + + if (currentClass != cachedClass) { + match = false; + break; + } + } + } + + if (match) { + // Gotcha! We've just found an entry in the cache + // that matched all passed arguments and temporaries. + + // We've found the specialization that may be used. + m_blockCacheHits++; + return slot.function; + } + } + + // If all associated slots are visited, + // but no matching entry is found then + // it is definitely a cache miss. + + // Sad but true + m_blockCacheMisses++; + return 0; +} + +void JITRuntime::fillBlockSlot( + JITRuntime::TBlockFunctionCacheEntry& slot, + TBlock* block, + JITRuntime::TBlockFunction function) +{ + const uint32_t blockOffset = block->blockBytePointer; + TMethod* const containerMethod = block->method; + + slot.containerMethod = containerMethod; + slot.blockOffset = blockOffset; + slot.function = function; + + const uint32_t closureMask = static_cast(block->stackTop.rawValue()) >> 1; + if (! closureMask) + return; + + if (uint32_t tempBits = closureMask & 0xFF) { + TObjectArray& temporaries = *block->temporaries; + + for (std::size_t index = 0; tempBits; index++, tempBits >>= 1) { + const bool tempIsUsed = tempBits & 1; + + if (! tempIsUsed) + continue; + + TObject* const field = temporaries[index]; + TClass* const currentClass = isSmallInteger(field) ? globals.smallIntClass : field->getClass(); + + slot.closures[index] = currentClass; + } + } + + if (uint32_t argBits = (closureMask >> 8) & 0xFF) { + TObjectArray& arguments = *block->arguments; + + for (std::size_t index = 0; argBits; index++, argBits >>= 1) { + const bool argIsUsed = argBits & 1; + + if (! argIsUsed) + continue; + + TObject* const field = arguments[index]; + TClass* const currentClass = isSmallInteger(field) ? globals.smallIntClass : field->getClass(); + + slot.closures[index + 8] = currentClass; + } + } } +void JITRuntime::updateBlockFunctionCache(TBlock* block, TBlockFunction function) +{ + // If closure mask is not defined then we could not cache the block. + // This happens either when block came from the SoftVM or because block + // accesses too many temporaries and/or arguments. + if (! (block->stackTop.rawValue() & 1)) + return; + + const uint32_t blockOffset = block->blockBytePointer; + TMethod* const containerMethod = block->method; + + // Closure mask is a bit mask representing indices of the temporaries (T) + // and arguments (A) that are accessed from within a block. + // Currently only 17 bits are defined: xxxxxxxxxxxxxxx|AAAAAAAA|TTTTTTTT|1 + + // The least significant bit is set to 1 to indicate that it is not an object pointer. + // It is not used in lookup logic, so we shift it out to make life easier: + const uint32_t closureMask = static_cast(block->stackTop.rawValue()) >> 1; + + // All block specializations share the same hash. If cache would be 1-way associative + // then constant cache rotation will occur as several specializations will compete + // against the single cache slot. It order to prevent this we use the n-way cache. + + const uint32_t hash = (reinterpret_cast(containerMethod) << 16) ^ (blockOffset << 8) ^ closureMask; + + // Outer loop iterates over the associated slots + for (std::size_t slotIndex = 0; slotIndex < BLOCK_CACHE_ASSOCIATIVITY; slotIndex++) { + TBlockFunctionCacheEntry& slot = m_blockFunctionLookupCache[(hash + slotIndex) % LOOKUP_CACHE_SIZE]; + + // We need to find a suitable slot preserving other friendly specializations if possible. + // If we'll find a slot occupied by a friendly specialization, then we'll try again. + if (slot.containerMethod == containerMethod && slot.blockOffset == blockOffset) + continue; // check the next slot + + // We have found a slot that may be used + fillBlockSlot(slot, block, function); + return; + } + + // It seem that all slots are occupied by the friendly specializations. + // We need to chose the slot within bounds and overwrite it by our data. + + // Bit shift is required to eliminate pointer alignment or it would not work. + const uint32_t slotIndex = (reinterpret_cast(function) >> 4) % METHOD_CACHE_ASSOCIATIVITY; + + TBlockFunctionCacheEntry& slot = m_blockFunctionLookupCache[(hash + slotIndex) % LOOKUP_CACHE_SIZE]; + fillBlockSlot(slot, block, function); +} void JITRuntime::optimizeFunction(Function* function, bool runModulePass) { @@ -295,40 +581,27 @@ void JITRuntime::invokeBlock(TBlock* block, TContext* callingContext, TReturnVal { m_blocksInvoked++; - // Guessing the block function name - const uint16_t blockOffset = block->blockBytePointer; - - TBlockFunction compiledBlockFunction = once ? 0 : lookupBlockFunctionInCache(block->method, blockOffset); + TBlockFunction compiledBlockFunction = once ? 0 : lookupBlockFunctionInCache(block); Function* blockFunction = 0; if (! compiledBlockFunction) { - std::ostringstream ss; - ss << block->method->klass->name->toString() << ">>" << block->method->name->toString() << "@" << blockOffset; - std::string blockFunctionName = ss.str(); + // Compiling function and storing it to the table for further use + blockFunction = m_methodCompiler->compileDynamicBlock(block); - blockFunction = m_JITModule->getFunction(blockFunctionName); if (!blockFunction) { - // Block functions are created when wrapping method gets compiled. - // If function was not found then the whole method needs compilation. - - // Compiling function and storing it to the table for further use - blockFunction = m_methodCompiler->compileBlock(block); - - if (!blockFunction) { - // Something is really wrong! - outs() << "JIT: Fatal error in invokeBlock for " << blockFunctionName << "\n"; - std::exit(1); - } + // Something is really wrong! + outs() << "JIT: Fatal error in invokeBlock\n"; + std::exit(1); + } // outs() << *blockFunction << "\n"; verifyModule(*m_JITModule, AbortProcessAction); - optimizeFunction(blockFunction, true); - } + optimizeFunction(blockFunction, true); // FIXME compiledBlockFunction = reinterpret_cast(m_executionEngine->getPointerToFunction(blockFunction)); - updateBlockFunctionCache(block->method, blockOffset, compiledBlockFunction); + updateBlockFunctionCache(block, compiledBlockFunction); } block->previousContext = callingContext->previousContext; @@ -350,37 +623,57 @@ void JITRuntime::sendMessage(TContext* callingContext, TSymbol* message, TObject hptr messageArguments = m_softVM->newPointer(arguments); TMethodFunction compiledMethodFunction = 0; - TContext* newContext = 0; - hptr previousContext = m_softVM->newPointer(callingContext); + + // Preparing the context object. Because we do not call the software + // implementation here, we do not need to allocate the stack object + // because it is not used by JIT runtime. We also may skip the proper + // initialization of various objects such as stackTop and bytePointer. + // Note: temporary object will be allocated by the function on it's frame. + + TContext contextSlot(globals.contextClass); + contextSlot.arguments = messageArguments; + contextSlot.previousContext = callingContext; + hptr newContext = m_softVM->newPointer(&contextSlot); { // First of all we need to find the actual method object if (!receiverClass) { - TObject* receiver = messageArguments[0]; + TObject* const receiver = messageArguments[0]; receiverClass = isSmallInteger(receiver) ? globals.smallIntClass : receiver->getClass(); } // Searching for the actual method to be called hptr method = m_softVM->newPointer(m_softVM->lookupMethod(message, receiverClass)); + newContext->method = method; // Checking whether we found a method if (method == 0) { - // Oops. Method was not found. In this case we should send #doesNotUnderstand: message to the receiver + // Oops. Method was not found. In this case we should + // send #doesNotUnderstand: message to the receiver. + + // TODO Refactor the code to eliminate memory allocation + // This will allow to completely remove hptr's m_softVM->setupVarsForDoesNotUnderstand(method, messageArguments, message, receiverClass); - // Continuing the execution just as if #doesNotUnderstand: was the actual selector that we wanted to call + + // Continuing the execution just as if #doesNotUnderstand: + // was the actual selector that we wanted to call } // Searching for the jit compiled function - compiledMethodFunction = lookupFunctionInCache(method); + compiledMethodFunction = lookupFunctionInCache(method, messageArguments); if (! compiledMethodFunction) { + type::Type argumentsType = type::createArgumentsType(messageArguments); + // If function was not found in the cache looking it in the LLVM directly - std::string functionName = method->klass->name->toString() + ">>" + method->name->toString(); + const std::string& functionName = type::getQualifiedMethodName(method, argumentsType); Function* methodFunction = m_JITModule->getFunction(functionName); if (! methodFunction) { + outs() << "Compiling dynamic method " << functionName << "\n"; + // Compiling function and storing it to the table for further use - methodFunction = m_methodCompiler->compileMethod(method); + methodFunction = m_methodCompiler->compileMethod(method, argumentsType); // outs() << *methodFunction << "\n"; @@ -391,7 +684,7 @@ void JITRuntime::sendMessage(TContext* callingContext, TSymbol* message, TObject // Calling the method and returning the result compiledMethodFunction = reinterpret_cast(m_executionEngine->getPointerToFunction(methodFunction)); - updateFunctionCache(method, compiledMethodFunction); + updateFunctionCache(method, compiledMethodFunction, messageArguments); // outs() << *methodFunction << "\n"; @@ -401,22 +694,7 @@ void JITRuntime::sendMessage(TContext* callingContext, TSymbol* message, TObject } // Updating call site statistics and scheduling method processing - updateHotSites(compiledMethodFunction, previousContext, message, receiverClass, callSiteIndex); - - // Preparing the context objects. Because we do not call the software - // implementation here, we do not need to allocate the stack object - // because it is not used by JIT runtime. We also may skip the proper - // initialization of various objects such as stackTop and bytePointer. - - // Creating context object and temporaries - hptr newTemps = m_softVM->newObject(method->temporarySize); - newContext = m_softVM->newObject(); - - // Initializing context variables - newContext->temporaries = newTemps; - newContext->arguments = messageArguments; - newContext->method = method; - newContext->previousContext = previousContext; + //updateHotSites(compiledMethodFunction, previousContext, message, receiverClass, callSiteIndex); } try { @@ -439,7 +717,7 @@ void JITRuntime::updateHotSites(TMethodFunction methodFunction, TContext* callin if (!callSiteIndex) return; - TMethodFunction callerMethodFunction = lookupFunctionInCache(callingContext->method); + TMethodFunction callerMethodFunction = lookupFunctionInCache(callingContext->method, 0); // TODO reload cache if callerMethodFunction was popped out if (!callerMethodFunction) @@ -494,7 +772,7 @@ void JITRuntime::patchHotMethods() // Compiling function from scratch outs() << "Recompiling method for patching: " << methodFunction->getName().str() << "\n"; Value* contextHolder = 0; - m_methodCompiler->compileMethod(method, methodFunction, &contextHolder); + m_methodCompiler->compileMethod(method, type::Type(), methodFunction, &contextHolder); outs() << "Patching " << hotMethod->methodFunction->getName().str() << " ..."; @@ -1099,9 +1377,9 @@ TByteObject* newBinaryObject(TClass* klass, uint32_t dataSize) return JITRuntime::Instance()->getVM()->newBinaryObject(klass, dataSize); } -TBlock* createBlock(TContext* callingContext, uint8_t argLocation, uint16_t bytePointer) +TBlock* createBlock(TContext* callingContext, uint8_t argLocation, uint16_t bytePointer, uint32_t stackTop) { - return JITRuntime::Instance()->createBlock(callingContext, argLocation, bytePointer); + return JITRuntime::Instance()->createBlock(callingContext, argLocation, bytePointer, stackTop); } void emitBlockReturn(TObject* value, TContext* targetContext) diff --git a/src/MethodCompiler.cpp b/src/MethodCompiler.cpp index 456afb9..9a773c1 100644 --- a/src/MethodCompiler.cpp +++ b/src/MethodCompiler.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include using namespace llvm; @@ -55,6 +56,46 @@ std::string to_string(const T& x) { return ss.str(); } +static Constant* createStringConstant(Module& M, char const* str, Twine const& name) { + LLVMContext& ctx = getGlobalContext(); + Constant* strConstant = ConstantDataArray::getString(ctx, str); + GlobalVariable* GVStr = + new GlobalVariable(M, strConstant->getType(), true, + GlobalValue::InternalLinkage, strConstant, name); + Constant* zero = Constant::getNullValue(IntegerType::getInt32Ty(ctx)); + Constant* indices[] = {zero, zero}; + Constant* strVal = ConstantExpr::getGetElementPtr(GVStr, indices, true); + return strVal; +} + +void MethodCompiler::insertTrace(MethodCompiler::TJITContext& jit, const char* message) { + Value* const print = m_JITModule->getFunction("printf"); + jit.builder->CreateCall(print, createStringConstant(*m_JITModule, message, "str.")); +} + +void MethodCompiler::insertTrace(MethodCompiler::TJITContext& jit, const char* message, llvm::Value* value) { + Value* const print = m_JITModule->getFunction("printf"); + jit.builder->CreateCall2(print, createStringConstant(*m_JITModule, message, "str."), value); +} + +MethodCompiler::MethodCompiler( + JITRuntime& runtime, + llvm::Module* JITModule, + TRuntimeAPI runtimeApi, + TExceptionAPI exceptionApi +) : + m_runtime(runtime), + m_JITModule(JITModule), + m_runtimeAPI(runtimeApi), + m_exceptionAPI(exceptionApi), + m_typeSystem(*runtime.getVM()), + m_callSiteIndex(1) +{ + m_baseTypes.initializeFromModule(JITModule); + m_globals.initializeFromModule(JITModule); + m_baseFunctions.initializeFromModule(JITModule); +} + Value* MethodCompiler::TJITContext::getLiteral(uint32_t index) { Value* const literal = builder->CreateCall2( @@ -78,7 +119,7 @@ Value* MethodCompiler::TJITContext::getMethodClass() return klass; } -Function* MethodCompiler::createFunction(TMethod* method) +Function* MethodCompiler::createFunction(TMethod* method, const std::string& functionName) { Type* const methodParams[] = { m_baseTypes.context->getPointerTo() }; FunctionType* const functionType = FunctionType::get( @@ -87,7 +128,6 @@ Function* MethodCompiler::createFunction(TMethod* method) false // we're not dealing with vararg ); - std::string functionName = method->klass->name->toString() + ">>" + method->name->toString(); Function* const function = cast( m_JITModule->getOrInsertFunction(functionName, functionType)); function->setCallingConv(CallingConv::C); //Anyway C-calling conversion is default function->setGC("shadow-stack"); @@ -133,7 +173,7 @@ Value* MethodCompiler::protectPointer(TJITContext& jit, Value* value) Value* MethodCompiler::protectProducerNode(TJITContext& jit, st::ControlNode* node, Value* value) { - if (shouldProtectProducer(jit.currentNode)) + if (shouldProtectProducer(jit, node)) return protectPointer(jit, value); else return value; // return value as is @@ -176,17 +216,20 @@ class Walker : public st::GraphWalker { private: Detector& m_detector; - virtual TVisitResult visitNode(st::ControlNode* node) { return m_detector.checkNode(node); } + + virtual TVisitResult visitNode(st::ControlNode& node, const TPathNode*) { + return m_detector.checkNode(&node); + } }; bool MethodCompiler::methodAllocatesMemory(TJITContext& jit) { - class GCDetector : public st::ForwardWalker { + class GCDetector : public st::GraphWalker { public: GCDetector() : m_detected(false) {} - virtual TVisitResult visitNode(st::ControlNode* node) { - if (st::InstructionNode* const candidate = node->cast()) { + virtual TVisitResult visitNode(st::ControlNode& node, const TPathNode*) { + if (st::InstructionNode* const candidate = node.cast()) { if (candidate->getInstruction().mayCauseGC()) { m_detected = true; return st::GraphWalker::vrStopWalk; @@ -203,13 +246,17 @@ bool MethodCompiler::methodAllocatesMemory(TJITContext& jit) }; GCDetector detector; - detector.run((*jit.controlGraph->begin())->getEntryPoint()); + detector.run((*jit.controlGraph->begin())->getEntryPoint(), GCDetector::wdForward, GCDetector::wtDepthFirst, true); return detector.isDetected(); } -bool MethodCompiler::shouldProtectProducer(st::ControlNode* producer) +bool MethodCompiler::shouldProtectProducer(TJITContext& jit, st::ControlNode* producer) { + const type::Type& type = jit.inferContext[*producer]; + if (type.isLiteral() || type.getValue() == globals.smallIntClass) + return false; + // We should protect the value by holder if consumer of this value is far away. // By far away we mean that it crosses the barrier of potential garbage collection. // For example if value is consumed right next to the point it was produced, then @@ -265,7 +312,7 @@ bool MethodCompiler::shouldProtectProducer(st::ControlNode* producer) for (st::TNodeSet::iterator iConsumer = consumers.begin(); iConsumer != consumers.end(); ++iConsumer) { st::ControlNode* const consumer = *iConsumer; - walker.run(consumer, st::GraphWalker::wdBackward); + walker.run(consumer, st::GraphWalker::wdBackward, st::GraphWalker::wtDepthFirst, true); if (detector.isDetected()) { return true; @@ -290,11 +337,9 @@ void MethodCompiler::writePreamble(TJITContext& jit, bool isBlock) context = jit.builder->CreateBitCast(blockContext, m_baseTypes.context->getPointerTo()); } - context->setName("contextParameter"); - // Protecting the context holder + // FIXME Temporary until block context is stack allocated jit.contextHolder = jit.methodAllocatesMemory ? protectPointer(jit, context) : context; - jit.contextHolder->setName("pContext"); // Storing self pointer Value* const pargs = jit.builder->CreateStructGEP(context, 2); @@ -304,12 +349,51 @@ void MethodCompiler::writePreamble(TJITContext& jit, bool isBlock) jit.selfHolder = jit.methodAllocatesMemory ? protectPointer(jit, self) : self; jit.selfHolder->setName("pSelf"); + + // Allocating context object and temporaries on the methodFunction's stack. + // This operation does not affect garbage collector, so no pointer protection + // is required. Moreover, this is operation is much faster than heap allocation. + const std::size_t tempsCount = jit.originMethod->temporarySize; + + if (tempsCount) { + if (isBlock) { + // TODO Protect pointer + Value* const ptemps = jit.builder->CreateStructGEP(context, 3); + Value* const temporaries = jit.builder->CreateLoad(ptemps); + jit.temporaries = jit.builder->CreateBitCast(temporaries, m_baseTypes.object->getPointerTo()); + } else { + const uint32_t tempsSize = sizeof(TObjectArray) + sizeof(TObject*) * tempsCount; + + MethodCompiler::TStackObject tempsPair = allocateStackObject(*jit.builder, sizeof(TObjectArray), tempsCount); + + jit.builder->CreateMemSet( + tempsPair.objectSlot, // destination address + jit.builder->getInt8(0), // fill with zeroes + tempsSize, // size of object slot + 0, // no alignment + false // volatile operation + ); + + Value* const newTempsObject = jit.builder->CreateBitCast(tempsPair.objectSlot, m_baseTypes.object->getPointerTo(), "temps."); + + const uint32_t tempsFieldsCount = tempsSize / sizeof(TObject*) - 2; + jit.builder->CreateCall2(getBaseFunctions().setObjectSize, newTempsObject, jit.builder->getInt32(tempsFieldsCount)); + jit.builder->CreateCall2(getBaseFunctions().setObjectClass, newTempsObject, getJitGlobals().arrayClass); + + Value* const contextObject = jit.builder->CreateBitCast(context, m_baseTypes.object->getPointerTo()); + jit.builder->CreateCall3(getBaseFunctions().setObjectField, contextObject, jit.builder->getInt32(2), newTempsObject); + + jit.temporaries = newTempsObject; + } + } + } Value* MethodCompiler::TJITContext::getCurrentContext() { + // FIXME Temporary until block context is stack allocated if (methodAllocatesMemory) - return builder->CreateLoad(contextHolder, "context."); + return builder->CreateLoad(contextHolder, "self."); else return contextHolder; } @@ -392,12 +476,24 @@ TObjectAndSize MethodCompiler::createArray(TJITContext& jit, uint32_t elementsCo return std::make_pair(arrayObject, arraySize); } -Function* MethodCompiler::compileMethod(TMethod* method, llvm::Function* methodFunction /*= 0*/, llvm::Value** contextHolder /*= 0*/) +Function* MethodCompiler::compileMethod(TMethod* method, const type::Type& arguments, llvm::Function* methodFunction /*= 0*/, llvm::Value** contextHolder /*= 0*/) { - TJITContext jit(this, method); + type::InferContext* const inferContext = m_typeSystem.inferMessage(method->name, arguments, 0); + assert(inferContext); + assert(inferContext->getMethod() == method); + + const std::string& methodName = type::getQualifiedMethodName(method, arguments); + printf("compiling method %s\n", methodName.c_str()); + + TJITContext jit(this, method, *inferContext); + + { + ControlGraphVisualizer vis(jit.controlGraph, methodName, "dots/"); + vis.run(); + } // Creating the function named as "Class>>method" or using provided one - jit.function = methodFunction ? methodFunction : createFunction(method); + jit.function = methodFunction ? methodFunction : createFunction(method, methodName); // Creating the preamble basic block and inserting it into the function // It will contain basic initialization code (args, temps and so on) @@ -669,11 +765,24 @@ void MethodCompiler::doPushArgument(TJITContext& jit) void MethodCompiler::doPushTemporary(TJITContext& jit) { const uint8_t index = jit.currentNode->getInstruction().getArgument(); - Value* const temporary = jit.builder->CreateCall2( + + Value* temporary = 0; + + if (jit.isBlock) { + temporary = jit.builder->CreateCall2( m_baseFunctions.getTemporary, jit.getCurrentContext(), jit.builder->getInt32(index) - ); + ); + } else { + temporary = jit.builder->CreateCall2( + m_baseFunctions.getObjectField, + jit.temporaries, + jit.builder->getInt32(index) + ); + } + + assert(temporary); temporary->setName(std::string("temp") + to_string(index) + "."); Value* const holder = protectProducerNode(jit, jit.currentNode, temporary); @@ -740,25 +849,59 @@ void MethodCompiler::doPushConstant(TJITContext& jit) setNodeValue(jit, jit.currentNode, constantValue); } +uint32_t getClosureMask(st::ControlGraph& blockGraph) +{ + // Closure mask is a bit mask representing indices of the temporaries (T) + // and arguments (A) that are accessed from within a block. + // Currently only 17 bits are defined: xxxxxxxxxxxxxxx|AAAAAAAA|TTTTTTTT|1 + // The least significant bit is set to 1 to indicate that it is not an object pointer. + + std::bitset<32> closureMask; + + closureMask[0] = 1; + + const st::ControlGraph::TMetaInfo::TIndexList& readsTemporaries = blockGraph.getMeta().readsTemporaries; + for (std::size_t index = 0; index < readsTemporaries.size(); index++) { + const uint32_t tempIndex = readsTemporaries[index]; + + if (tempIndex < 8) + closureMask[tempIndex + 1] = 1; + else + return 0; // method could not be cashed + } + + const st::ControlGraph::TMetaInfo::TIndexList& readsArguments = blockGraph.getMeta().readsArguments; + for (std::size_t index = 0; index < readsArguments.size(); index++) { + const uint32_t argIndex = readsArguments[index]; + + if (argIndex < 8) + closureMask[argIndex + 8 + 1] = 1; + else + return 0; // method could not be cashed + } + + return closureMask.to_ulong(); +} + void MethodCompiler::doPushBlock(TJITContext& jit) { + // TODO Check that this block is actually used/invoked and not escapes. + st::PushBlockNode* const pushBlockNode = jit.currentNode->cast(); st::ParsedBlock* const parsedBlock = pushBlockNode->getParsedBlock(); const uint16_t blockOffset = parsedBlock->getStartOffset(); - std::ostringstream ss; - ss << jit.originMethod->klass->name->toString() + ">>" + jit.originMethod->name->toString() << "@" << blockOffset; - std::string blockFunctionName = ss.str(); + st::ControlGraph* const blockGraph = m_typeSystem.getBlockGraph(parsedBlock); - // If block function is not already created, create it - if (! m_JITModule->getFunction(blockFunctionName)) - compileBlock(jit, blockFunctionName, parsedBlock); + const uint32_t argumentLocation = jit.currentNode->getInstruction().getArgument(); + const uint32_t closureMask = getClosureMask(*blockGraph); // Create block object and fill it with context information Value* const args[] = { - jit.getCurrentContext(), // creatingContext - jit.builder->getInt8(jit.currentNode->getInstruction().getArgument()), // arg offset - jit.builder->getInt16(blockOffset) + jit.getCurrentContext(), // creatingContext + jit.builder->getInt8(argumentLocation), // arg offset + jit.builder->getInt16(blockOffset), // block byte pointer + jit.builder->getInt32(closureMask) // closure mask as stack top }; Value* blockObject = jit.builder->CreateCall(m_runtimeAPI.createBlock, args); @@ -769,25 +912,150 @@ void MethodCompiler::doPushBlock(TJITContext& jit) setNodeValue(jit, jit.currentNode, blockHolder); } -llvm::Function* MethodCompiler::compileBlock(TBlock* block) +void createBlockTypes( + TBlock* block, + st::ControlGraph& blockGraph, + type::Type& blockType, + type::Type& blockArguments, + type::Type& blockTemps, + type::InferContext::TVariableMap& closureTypes) +{ + blockType.set(globals.blockClass, type::Type::tkMonotype); + blockType.pushSubType(block->method); // [Type::bstOrigin] + blockType.pushSubType(type::Type(TInteger(block->blockBytePointer))); // [Type::bstOffset] + blockType.pushSubType(type::Type(TInteger(block->argumentLocation))); // [Type::bstArgIndex] + blockType.pushSubType(type::Type(TInteger(0))); // [Type::bstContextIndex] + + blockArguments.set(globals.arrayClass, type::Type::tkArray); + + TObjectArray& temporaries = * block->temporaries; + const uint32_t argumentLocation = block->argumentLocation; + + const st::ControlGraph::TMetaInfo::TIndexList& readsTemporaries = blockGraph.getMeta().readsTemporaries; + const st::ControlGraph::TMetaInfo::TIndexList& writesTemporaries = blockGraph.getMeta().writesTemporaries; + + type::Type readIndices(globals.arrayClass, type::Type::tkArray); + type::Type writeIndices(globals.arrayClass, type::Type::tkArray); + + for (std::size_t index = 0; index < readsTemporaries.size(); index++) { + const uint32_t tempIndex = readsTemporaries[index]; + TObject* const argument = temporaries[tempIndex]; + TClass* const klass = isSmallInteger(argument) ? globals.smallIntClass : argument->getClass(); + const type::Type newType(klass); + + if (readsTemporaries[index] < argumentLocation) { + closureTypes[tempIndex] = newType; + blockTemps.pushSubType(newType); + readIndices.pushSubType(type::Type(TInteger(tempIndex))); + } else { + blockArguments.pushSubType(newType); + } + } + + for (std::size_t index = 0; index < writesTemporaries.size(); index++) { + // We're interested only in temporaries from lexical context, not block arguments + if (writesTemporaries[index] >= argumentLocation) + continue; + + writeIndices.pushSubType(type::Type(TInteger(writesTemporaries[index]))); + } + + blockType.pushSubType(readIndices); // [Type::bstReadsTemps] + blockType.pushSubType(writeIndices); // [Type::bstWritesTemps] + blockType.pushSubType(type::Type(TInteger(0))); // [Type::bstCaptureIndex] +} + +std::string getDynamicBlockFunctionName(const type::Type& blockType, const type::Type& blockArguments, const type::Type& blockTemps) +{ + return blockArguments.toString() + blockTemps.toString() + "::" + blockType.toString(); +} + +std::string getStaticBlockFunctionName(const type::Type& blockType, const type::Type& blockArguments) +{ + return blockArguments.toString() + "::" + blockType.toString(); +} + +llvm::Function* MethodCompiler::compileDynamicBlock(TBlock* block) { - TJITContext methodContext(this, block->method); const uint16_t blockOffset = block->blockBytePointer; + st::ControlGraph* const methodGraph = m_typeSystem.getMethodGraph(block->method); + st::ParsedBlock* const parsedBlock = methodGraph->getParsedMethod()->getParsedBlockByOffset(blockOffset); + st::ControlGraph* const blockGraph = m_typeSystem.getBlockGraph(parsedBlock); - std::ostringstream ss; - ss << block->method->klass->name->toString() << ">>" << block->method->name->toString() << "@" << blockOffset; - const std::string blockFunctionName = ss.str(); + const type::Type& methodArguments = type::createArgumentsType(block->arguments); + + type::InferContext methodContext(block->method, 0, methodArguments); + type::InferContext::TVariableMap& closureTypes = methodContext.getBlockClosures()[0]; + + type::Type blockType; + type::Type blockArguments(globals.arrayClass, type::Type::tkArray); + type::Type blockTemps(globals.arrayClass, type::Type::tkArray); + createBlockTypes(block, *blockGraph, blockType, blockArguments, blockTemps, closureTypes); - st::ParsedBlock* const parsedBlock = methodContext.parsedMethod->getParsedBlockByOffset(blockOffset); - assert(parsedBlock); + const uint32_t closureMask = getClosureMask(*blockGraph); + block->stackTop.setRawValue(closureMask); - return compileBlock(methodContext, blockFunctionName, parsedBlock); + // Check if function was already compiled + const std::string& blockFunctionName = getDynamicBlockFunctionName(blockType, blockArguments, blockTemps); + if (Function* const blockFunction = m_JITModule->getFunction(blockFunctionName)) + return blockFunction; + + printf("compiling dynamic block %s\n", blockFunctionName.c_str()); + + type::TContextStack stack(methodContext); + type::InferContext* const blockInferContext = m_typeSystem.inferDynamicBlock(blockType, blockArguments, blockTemps, &stack); + assert(blockInferContext); + + return compileBlock(blockFunctionName, parsedBlock, *blockInferContext); } -llvm::Function* MethodCompiler::compileBlock(TJITContext& jit, const std::string& blockFunctionName, st::ParsedBlock* parsedBlock) +llvm::Function* MethodCompiler::compileInferredBlock(TJITContext& jit) { - const uint16_t blockOffset = parsedBlock->getStartOffset(); - TJITBlockContext blockContext(this, jit.parsedMethod, parsedBlock); + type::Type& blockType = jit.inferContext[*jit.currentNode->getArgument()]; + if (! blockType.isBlock()) + return 0; + + type::Type blockArguments(type::Type::tkArray); + + if (jit.currentNode->getArgumentsCount() > 1) + blockArguments.pushSubType(jit.inferContext[*jit.currentNode->getArgument(1)]); + + if (jit.currentNode->getArgumentsCount() > 2) + blockArguments.pushSubType(jit.inferContext[*jit.currentNode->getArgument(2)]); + + type::InferContext* const blockContext = m_typeSystem.inferBlock(blockType, blockArguments, 0); + + if (! blockContext) + return 0; + + const std::string& blockFunctionName = getStaticBlockFunctionName(blockType, blockArguments); + if (Function* const blockFunction = m_JITModule->getFunction(blockFunctionName)) + return blockFunction; + + TMethod* const method = blockType[type::Type::bstOrigin].getValue()->cast(); + const uint16_t blockOffset = TInteger(blockType[type::Type::bstOffset].getValue()); + + st::ControlGraph* const methodGraph = m_typeSystem.getMethodGraph(method); + st::ParsedBlock* const parsedBlock = methodGraph->getParsedMethod()->getParsedBlockByOffset(blockOffset); + + printf("compiling inferred block %s\n", blockFunctionName.c_str()); + return compileBlock(blockFunctionName, parsedBlock, *blockContext); +} + +llvm::Function* MethodCompiler::compileBlock(const std::string& blockFunctionName, st::ParsedBlock* parsedBlock, type::InferContext& blockcontext) +{ + // Check if function was already compiled + if (Function* const blockFunction = m_JITModule->getFunction(blockFunctionName)) + return blockFunction; + + st::ControlGraph* const blockGraph = m_typeSystem.getBlockGraph(parsedBlock); + + TJITBlockContext blockContext(this, parsedBlock->getContainer(), parsedBlock, blockGraph, blockcontext); + + { + ControlGraphVisualizer vis(blockContext.controlGraph, blockFunctionName, "dots/"); + vis.run(); + } std::vector blockParams; blockParams.push_back(m_baseTypes.block->getPointerTo()); // block object with context information @@ -798,59 +1066,58 @@ llvm::Function* MethodCompiler::compileBlock(TJITContext& jit, const std::string false // we're not dealing with vararg ); - blockContext.function = m_JITModule->getFunction(blockFunctionName); - if (! blockContext.function) { // Checking if not already created - blockContext.function = cast(m_JITModule->getOrInsertFunction(blockFunctionName, blockFunctionType)); + blockContext.function = cast(m_JITModule->getOrInsertFunction(blockFunctionName, blockFunctionType)); - blockContext.function->setGC("shadow-stack"); - m_blockFunctions[blockFunctionName] = blockContext.function; + blockContext.function->setGC("shadow-stack"); + m_blockFunctions[blockFunctionName] = blockContext.function; - // Creating the basic block and inserting it into the function - blockContext.preamble = BasicBlock::Create(m_JITModule->getContext(), "blockPreamble", blockContext.function); - blockContext.builder = new IRBuilder<>(blockContext.preamble); - writePreamble(blockContext, /*isBlock*/ true); + // Creating the basic block and inserting it into the function + blockContext.preamble = BasicBlock::Create(m_JITModule->getContext(), "blockPreamble", blockContext.function); + blockContext.builder = new IRBuilder<>(blockContext.preamble); + writePreamble(blockContext, /*isBlock*/ true); - writeUnwindBlockReturn(blockContext); + writeUnwindBlockReturn(blockContext); - scanForBranches(blockContext, blockContext.parsedBlock); + scanForBranches(blockContext, blockContext.parsedBlock); - std::stringstream ss; - ss.str(""); - ss << "offset" << blockOffset; + const uint16_t blockOffset = parsedBlock->getStartOffset(); - BasicBlock* const blockBody = parsedBlock->getBasicBlockByOffset(blockOffset)->getValue(); // m_targetToBlockMap[blockOffset]; - assert(blockBody); - blockBody->setName(ss.str()); + std::stringstream ss; + ss.str(""); + ss << "offset" << blockOffset; - blockContext.builder->SetInsertPoint(blockContext.preamble); - blockContext.builder->CreateBr(blockBody); + BasicBlock* const blockBody = parsedBlock->getBasicBlockByOffset(blockOffset)->getValue(); // m_targetToBlockMap[blockOffset]; + assert(blockBody); + blockBody->setName(ss.str()); - blockContext.builder->SetInsertPoint(blockBody); + blockContext.builder->SetInsertPoint(blockContext.preamble); + blockContext.builder->CreateBr(blockBody); - writeFunctionBody(blockContext); + blockContext.builder->SetInsertPoint(blockBody); - // Erasing unwind phi if it is not needed - // Block will be removed by DCE - // TODO Better to detect it before unwind block is written - if (! blockContext.unwindBlockReturn->hasNUsesOrMore(1)) { - if (blockContext.unwindPhi->getNumIncomingValues()) { - outs() << *blockContext.function << "\n"; - outs() << "Fatal error: phi is used but the block isn't!\n"; - abort(); - } + writeFunctionBody(blockContext); - blockContext.unwindPhi->replaceAllUsesWith(UndefValue::get(m_baseTypes.returnValueType)); - blockContext.unwindPhi->eraseFromParent(); - blockContext.unwindPhi = 0; + // Erasing unwind phi if it is not needed + // Block will be removed by DCE + // TODO Better to detect it before unwind block is written + if (! blockContext.unwindBlockReturn->hasNUsesOrMore(1)) { + if (blockContext.unwindPhi->getNumIncomingValues()) { + outs() << *blockContext.function << "\n"; + outs() << "Fatal error: phi is used but the block isn't!\n"; + abort(); } - // outs() << *blockContext.function << "\n"; + blockContext.unwindPhi->replaceAllUsesWith(UndefValue::get(m_baseTypes.returnValueType)); + blockContext.unwindPhi->eraseFromParent(); + blockContext.unwindPhi = 0; + } + + outs() << *blockContext.function << "\n"; - verifyFunction(*blockContext.function); + verifyFunction(*blockContext.function); - // Running optimization passes on a block function - JITRuntime::Instance()->optimizeFunction(blockContext.function, false); - } + // Running optimization passes on a block function + //JITRuntime::Instance()->optimizeFunction(blockContext.function, false); return blockContext.function; } @@ -860,12 +1127,21 @@ void MethodCompiler::doAssignTemporary(TJITContext& jit) const uint8_t index = jit.currentNode->getInstruction().getArgument(); Value* const value = getArgument(jit); - jit.builder->CreateCall3( + if (jit.isBlock) { + jit.builder->CreateCall3( m_baseFunctions.setTemporary, jit.getCurrentContext(), jit.builder->getInt32(index), value - ); + ); + } else { + jit.builder->CreateCall3( + m_baseFunctions.setObjectField, + jit.temporaries, + jit.builder->getInt32(index), + value + ); + } } void MethodCompiler::doAssignInstance(TJITContext& jit) @@ -900,8 +1176,8 @@ void MethodCompiler::doMarkArguments(TJITContext& jit) Value* const argumentsArray = jit.builder->CreateBitCast(argumentsObject, m_baseTypes.objectArray->getPointerTo()); Value* const argumentsPointer = jit.builder->CreateBitCast(argumentsObject, jit.builder->getInt8PtrTy()); - Function* gcrootIntrinsic = getDeclaration(m_JITModule, Intrinsic::lifetime_start); - jit.builder->CreateCall2(gcrootIntrinsic, jit.builder->getInt64(sizeInBytes), argumentsPointer); + Function* lifetimeStartIntrinsic = getDeclaration(m_JITModule, Intrinsic::lifetime_start); + jit.builder->CreateCall2(lifetimeStartIntrinsic, jit.builder->getInt64(sizeInBytes), argumentsPointer); setNodeValue(jit, jit.currentNode, argumentsArray); } @@ -1030,29 +1306,55 @@ void MethodCompiler::setNodeValue(TJITContext& jit, st::ControlNode* node, llvm: void MethodCompiler::doSendBinary(TJITContext& jit) { + const type::Type& leftType = jit.inferContext[*jit.currentNode->getArgument(0)].fold(); + const type::Type& rightType = jit.inferContext[*jit.currentNode->getArgument(1)].fold(); + const type::Type& resultType = jit.inferContext[*jit.currentNode].fold(); + + if (leftType.isLiteral() && rightType.isLiteral()) { + // TODO Extend to all literals + if (isSmallInteger(resultType.getValue())) { + ConstantInt* const rawResult = jit.builder->getInt32(TInteger(resultType.getValue())); + Value* const result = jit.builder->CreateCall(m_baseFunctions.newInteger, rawResult); + setNodeValue(jit, jit.currentNode, result); + return; + } + } + + // Literal int or (SmallInt) monotype + const bool leftTypeIsInt = isSmallInteger(leftType.getValue()) || leftType.getValue() == globals.smallIntClass; + const bool rightTypeIsInt = isSmallInteger(rightType.getValue()) || rightType.getValue() == globals.smallIntClass; + const bool encodeDirectIntegers = leftTypeIsInt && rightTypeIsInt; + // 0, 1 or 2 for '<', '<=' or '+' respectively binaryBuiltIns::Operator opcode = static_cast(jit.currentNode->getInstruction().getArgument()); - Value* const rightValue = getArgument(jit, 1); // jit.popValue(); Value* const leftValue = getArgument(jit, 0); // jit.popValue(); + Value* const rightValue = getArgument(jit, 1); // jit.popValue(); - // Checking if values are both small integers - Value* const rightIsInt = jit.builder->CreateCall(m_baseFunctions.isSmallInteger, rightValue); - Value* const leftIsInt = jit.builder->CreateCall(m_baseFunctions.isSmallInteger, leftValue); - Value* const isSmallInts = jit.builder->CreateAnd(rightIsInt, leftIsInt); + BasicBlock* integersBlock = 0; + BasicBlock* sendBinaryBlock = 0; + BasicBlock* resultBlock = 0; - BasicBlock* const integersBlock = BasicBlock::Create(m_JITModule->getContext(), "asIntegers.", jit.function); - BasicBlock* const sendBinaryBlock = BasicBlock::Create(m_JITModule->getContext(), "asObjects.", jit.function); - BasicBlock* const resultBlock = BasicBlock::Create(m_JITModule->getContext(), "result.", jit.function); + if (! encodeDirectIntegers) { + // Checking if values are both small integers + Value* const leftIsInt = jit.builder->CreateCall(m_baseFunctions.isSmallInteger, leftValue); + Value* const rightIsInt = jit.builder->CreateCall(m_baseFunctions.isSmallInteger, rightValue); + Value* const isSmallInts = jit.builder->CreateAnd(rightIsInt, leftIsInt); - // Depending on the contents we may either do the integer operations - // directly or create a send message call using operand objects - jit.builder->CreateCondBr(isSmallInts, integersBlock, sendBinaryBlock); + integersBlock = BasicBlock::Create(m_JITModule->getContext(), "asIntegers.", jit.function); + sendBinaryBlock = BasicBlock::Create(m_JITModule->getContext(), "asObjects.", jit.function); + resultBlock = BasicBlock::Create(m_JITModule->getContext(), "result.", jit.function); + + // Depending on the contents we may either do the integer operations + // directly or create a send message call using operand objects + jit.builder->CreateCondBr(isSmallInts, integersBlock, sendBinaryBlock); + + jit.builder->SetInsertPoint(integersBlock); + } // Now the integers part - jit.builder->SetInsertPoint(integersBlock); - Value* const rightInt = jit.builder->CreateCall(m_baseFunctions.getIntegerValue, rightValue); Value* const leftInt = jit.builder->CreateCall(m_baseFunctions.getIntegerValue, leftValue); + Value* const rightInt = jit.builder->CreateCall(m_baseFunctions.getIntegerValue, rightValue); Value* intResult = 0; // this will be an immediate operation result Value* intResultObject = 0; // this will be actual object to return @@ -1080,6 +1382,11 @@ void MethodCompiler::doSendBinary(TJITContext& jit) intResultObject->setName("bool."); } + if (encodeDirectIntegers) { + setNodeValue(jit, jit.currentNode, intResultObject); + return; + } + // Jumping out the integersBlock to the value aggregator jit.builder->CreateBr(resultBlock); @@ -1087,6 +1394,7 @@ void MethodCompiler::doSendBinary(TJITContext& jit) jit.builder->SetInsertPoint(sendBinaryBlock); // We need to create an arguments array and fill it with argument objects // Then send the message just like ordinary one + // TODO direct call in case of inferred context // Now creating the argument array TObjectAndSize array = createArray(jit, 2); @@ -1143,87 +1451,33 @@ void MethodCompiler::doSendBinary(TJITContext& jit) setNodeValue(jit, jit.currentNode, resultHolder); } - -bool MethodCompiler::doSendMessageToLiteral(TJITContext& jit, st::InstructionNode* receiverNode, TClass* receiverClass /*= 0*/) +void MethodCompiler::doSendInferredMessage(TJITContext& jit, type::InferContext& context) { - // Optimized version of doSendMessage which takes into account that - // pending message should be sent to the literal receiver - // (either constant or a member of method literals). Literal receivers - // are encoded at the time of method compilation, so thier value and - // their class will not change over time. Moreover, actual values - // are known at compile time and may be used to lookup the actual - // method that should be invoked. - - // Locating message selector - TSymbolArray& literals = *jit.originMethod->literals; - TSymbol* const messageSelector = literals[jit.currentNode->getInstruction().getArgument()]; - - // Determining receiver class - if (!receiverClass) { - TObject* literalReceiver = 0; - - const st::TSmalltalkInstruction::TOpcode opcode = receiverNode->getInstruction().getOpcode(); - if (opcode == opcode::pushLiteral) { - literalReceiver = literals[receiverNode->getInstruction().getArgument()]; - } else if (opcode == opcode::pushConstant) { - const uint8_t constant = receiverNode->getInstruction().getArgument(); - switch(constant) { - case 0: case 1: case 2: case 3: case 4: - case 5: case 6: case 7: case 8: case 9: - literalReceiver = TInteger(constant); - break; - - case pushConstants::nil: literalReceiver = globals.nilObject; break; - case pushConstants::trueObject: literalReceiver = globals.trueObject; break; - case pushConstants::falseObject: literalReceiver = globals.falseObject; break; - } - } - - assert(literalReceiver); - receiverClass = isSmallInteger(literalReceiver) ? globals.smallIntClass : literalReceiver->getClass(); - } - - assert(receiverClass); + // Inferred messages are easy to dispatch because we know + // virtually everything about their execution context. + // Therefore we may encode direct method call and optimize + // all stuff like as GC roots or checks that is redundant. - // Locating a method suitable for a direct call - TMethod* const directMethod = m_runtime.getVM()->lookupMethod(messageSelector, receiverClass); + TMethod* const directMethod = context.getMethod(); - if (! directMethod) { - outs() << "Error! Could not lookup method for class " << receiverClass->name->toString() << ", selector " << messageSelector->toString() << "\n"; - return false; - } - - std::string directFunctionName = directMethod->klass->name->toString() + ">>" + messageSelector->toString(); + const std::string& directFunctionName = context.getQualifiedName(); Function* directFunction = m_JITModule->getFunction(directFunctionName); if (!directFunction) { // Compiling function and storing it to the table for further use - directFunction = compileMethod(directMethod); + directFunction = compileMethod(directMethod, context.getArguments()); -// outs() << *directFunction << "\n"; - - verifyFunction(*directFunction , llvm::AbortProcessAction); - - m_runtime.optimizeFunction(directFunction, false); + outs() << *directFunction << "\n"; } - // Allocating context object and temporaries on the methodFunction's stack. - // This operation does not affect garbage collector, so no pointer protection - // is required. Moreover, this is operation is much faster than heap allocation. - const bool hasTemporaries = directMethod->temporarySize > 0; + // Allocating context object on the methodFunction's stack. This operation + // does not affect garbage collector, so no pointer protection is required. + // Moreover, this is operation is much faster than heap allocation. const uint32_t contextSize = sizeof(TContext); - const uint32_t tempsSize = hasTemporaries ? sizeof(TObjectArray) + sizeof(TObject*) * directMethod->temporarySize : 0; - - // Allocating stack space for objects and registering GC protection holder - MethodCompiler::TStackObject contextPair = allocateStackObject(*jit.builder, sizeof(TContext), 0); + // Allocating stack space for context and registering GC protection holder + TStackObject contextPair = allocateStackObject(*jit.builder, sizeof(TContext), 0); Value* const contextSlot = contextPair.objectSlot; - Value* tempsSlot = 0; - - if (hasTemporaries) { - MethodCompiler::TStackObject tempsPair = allocateStackObject(*jit.builder, sizeof(TObjectArray), directMethod->temporarySize); - tempsSlot = tempsPair.objectSlot; - } // Filling stack space with zeroes jit.builder->CreateMemSet( @@ -1234,21 +1488,11 @@ bool MethodCompiler::doSendMessageToLiteral(TJITContext& jit, st::InstructionNod false // volatile operation ); - if (hasTemporaries) - jit.builder->CreateMemSet( - tempsSlot, // destination address - jit.builder->getInt8(0), // fill with zeroes - tempsSize, // size of object slot - 0, // no alignment - false // volatile operation - ); - // Initializing object fields // TODO Move the init sequence out of the block or check that it is correctly optimized in loops - Value* const newContextObject = jit.builder->CreateBitCast(contextSlot, m_baseTypes.object->getPointerTo(), "newContext."); - Value* const newTempsObject = hasTemporaries ? jit.builder->CreateBitCast(tempsSlot, m_baseTypes.object->getPointerTo(), "newTemps.") : 0; - Function* const setObjectSize = getBaseFunctions().setObjectSize; - Function* const setObjectClass = getBaseFunctions().setObjectClass; + Value* const newContextObject = jit.builder->CreateBitCast(contextSlot, m_baseTypes.object->getPointerTo(), "newContext."); + Function* const setObjectSize = getBaseFunctions().setObjectSize; + Function* const setObjectClass = getBaseFunctions().setObjectClass; // Object size stored in the TSize field of any ordinary object contains // number of pointers except for the first two fields @@ -1257,12 +1501,6 @@ bool MethodCompiler::doSendMessageToLiteral(TJITContext& jit, st::InstructionNod jit.builder->CreateCall2(setObjectSize, newContextObject, jit.builder->getInt32(contextFieldsCount)); jit.builder->CreateCall2(setObjectClass, newContextObject, getJitGlobals().contextClass); - if (hasTemporaries) { - const uint32_t tempsFieldsCount = tempsSize / sizeof(TObject*) - 2; - jit.builder->CreateCall2(setObjectSize, newTempsObject, jit.builder->getInt32(tempsFieldsCount)); - jit.builder->CreateCall2(setObjectClass, newTempsObject, getJitGlobals().arrayClass); - } - Function* const setObjectField = getBaseFunctions().setObjectField; Value* const methodRawPointer = jit.builder->getInt32(reinterpret_cast(directMethod)); Value* const directMethodObject = jit.builder->CreateIntToPtr(methodRawPointer, m_baseTypes.object->getPointerTo()); @@ -1275,34 +1513,16 @@ bool MethodCompiler::doSendMessageToLiteral(TJITContext& jit, st::InstructionNod jit.builder->CreateCall3(setObjectField, newContextObject, jit.builder->getInt32(0), directMethodObject); jit.builder->CreateCall3(setObjectField, newContextObject, jit.builder->getInt32(1), messageArgumentsObject); - if (hasTemporaries) - jit.builder->CreateCall3(setObjectField, newContextObject, jit.builder->getInt32(2), newTempsObject); - else - jit.builder->CreateCall3(setObjectField, newContextObject, jit.builder->getInt32(2), getJitGlobals().nilObject); + // Note: temporaries (2) will be allocated on the stack frame of the message handler jit.builder->CreateCall3(setObjectField, newContextObject, jit.builder->getInt32(3), contextObject); Value* const newContext = jit.builder->CreateBitCast(newContextObject, m_baseTypes.context->getPointerTo()); - Value* result = 0; - - if (jit.methodHasBlockReturn) { - // Creating basic block that will be branched to on normal invoke - BasicBlock* const nextBlock = BasicBlock::Create(m_JITModule->getContext(), "next.", jit.function); - jit.currentNode->getDomain()->getBasicBlock()->setEndValue(nextBlock); - - // Performing a function invoke - result = jit.builder->CreateInvoke(directFunction, nextBlock, jit.exceptionLandingPad, newContext); - - // Switching builder to a new block - jit.builder->SetInsertPoint(nextBlock); - } else { - // Just calling the function. No block switching is required - result = jit.builder->CreateCall(directFunction, newContext); - } + Value* const result = jit.builder->CreateCall(directFunction, newContext); AllocaInst* const allocaInst = dyn_cast(jit.currentNode->getArgument()->getValue()->stripPointerCasts()); - Function* const gcrootIntrinsic = getDeclaration(m_JITModule, Intrinsic::lifetime_end); + Function* const lifetimeEndIntrinsic = getDeclaration(m_JITModule, Intrinsic::lifetime_end); Value* const argumentsPointer = jit.builder->CreateBitCast(arguments, jit.builder->getInt8PtrTy()); - jit.builder->CreateCall2(gcrootIntrinsic, jit.builder->CreateZExt(allocaInst->getArraySize(), jit.builder->getInt64Ty()), argumentsPointer); + jit.builder->CreateCall2(lifetimeEndIntrinsic, jit.builder->CreateZExt(allocaInst->getArraySize(), jit.builder->getInt64Ty()), argumentsPointer); Value* const targetContext = jit.builder->CreateExtractValue(result, 1, "targetContext"); Value* const isBlockReturn = jit.builder->CreateIsNotNull(targetContext); @@ -1315,43 +1535,28 @@ bool MethodCompiler::doSendMessageToLiteral(TJITContext& jit, st::InstructionNod Value* const resultObject = jit.builder->CreateExtractValue(result, 0, "resultObject"); Value* const resultHolder = protectProducerNode(jit, jit.currentNode, resultObject); setNodeValue(jit, jit.currentNode, resultHolder); - -// Value* const resultHolder = protectProducerNode(jit, jit.currentNode, result); -// setNodeValue(jit, jit.currentNode, resultHolder); - - return true; } void MethodCompiler::doSendMessage(TJITContext& jit) { + const type::Type& arguments = jit.inferContext[*jit.currentNode->getArgument()]; - st::InstructionNode* const markArgumentsNode = jit.currentNode->getArgument()->cast(); - assert(markArgumentsNode); - - st::InstructionNode* const receiverNode = markArgumentsNode->getArgument()->cast(); - assert(receiverNode); - - const st::TSmalltalkInstruction instruction = receiverNode->getInstruction(); + if (arguments.isArray() && !arguments.getSubTypes().empty()) { + TSymbolArray& literals = *jit.originMethod->literals; + const uint32_t literalIndex = jit.currentNode->getInstruction().getArgument(); + TSymbol* const selector = literals[literalIndex]; - // In case of a literal receiver we may encode direct method call - if (instruction.getOpcode() == opcode::pushLiteral || - instruction.getOpcode() == opcode::pushConstant) - { - if (doSendMessageToLiteral(jit, receiverNode)) + if (type::InferContext* const messageContext = m_typeSystem.inferMessage(selector, arguments, 0, false)) { + doSendInferredMessage(jit, *messageContext); return; - } - - // We may optimize send to self if clas does not have children - // TODO More optimization possible: send to super, if chiildren do not override selector - if (instruction.getOpcode() == opcode::pushArgument && instruction.getArgument() == 0) - { - TClass* const receiverClass = jit.originMethod->klass; - if (receiverClass->package == globals.nilObject) { - if (doSendMessageToLiteral(jit, receiverNode, receiverClass)) - return; } } + doSendGenericMessage(jit); +} + +void MethodCompiler::doSendGenericMessage(TJITContext& jit) +{ Value* const arguments = getArgument(jit); // jit.popValue(); // First of all we need to get the actual message selector @@ -1731,6 +1936,7 @@ void MethodCompiler::compilePrimitive(TJITContext& jit, break; case primitive::blockInvoke: { // 8 + Value* const object = getArgument(jit); // jit.popValue(); Value* const block = jit.builder->CreateBitCast(object, m_baseTypes.block->getPointerTo()); @@ -1738,21 +1944,23 @@ void MethodCompiler::compilePrimitive(TJITContext& jit, Value* const blockAsContext = jit.builder->CreateBitCast(block, m_baseTypes.context->getPointerTo()); Value* const blockTemps = jit.builder->CreateCall(m_baseFunctions.getTemps, blockAsContext); - Value* const tempsSize = jit.builder->CreateCall(m_baseFunctions.getObjectSize, blockTemps, "tempsSize."); +// Value* const tempsSize = jit.builder->CreateCall(m_baseFunctions.getObjectSize, blockTemps, "tempsSize."); Value* const argumentLocationPtr = jit.builder->CreateStructGEP(block, 1); Value* const argumentLocationField = jit.builder->CreateLoad(argumentLocationPtr); Value* const argumentLocationObject = jit.builder->CreateIntToPtr(argumentLocationField, m_baseTypes.object->getPointerTo()); Value* const argumentLocation = jit.builder->CreateCall(m_baseFunctions.getIntegerValue, argumentLocationObject, "argLocation."); - BasicBlock* const tempsChecked = BasicBlock::Create(m_JITModule->getContext(), "tempsChecked.", jit.function); - tempsChecked->moveAfter(jit.builder->GetInsertBlock()); +// BasicBlock* const tempsChecked = BasicBlock::Create(m_JITModule->getContext(), "tempsChecked.", jit.function); +// tempsChecked->moveAfter(jit.builder->GetInsertBlock()); + // FIXME Check will lead to a crash if containing method have no temporaries at all + // TODO Remove the check if block is inferred //Checking the passed temps size TODO unroll stack - Value* const blockAcceptsArgCount = jit.builder->CreateSub(tempsSize, argumentLocation, "blockAcceptsArgCount."); - Value* const tempSizeOk = jit.builder->CreateICmpSLE(jit.builder->getInt32(argCount), blockAcceptsArgCount, "tempSizeOk."); - jit.builder->CreateCondBr(tempSizeOk, tempsChecked, primitiveFailedBB); - jit.builder->SetInsertPoint(tempsChecked); +// Value* const blockAcceptsArgCount = jit.builder->CreateSub(tempsSize, argumentLocation, "blockAcceptsArgCount."); +// Value* const tempSizeOk = jit.builder->CreateICmpSLE(jit.builder->getInt32(argCount), blockAcceptsArgCount, "tempSizeOk."); +// jit.builder->CreateCondBr(tempSizeOk, tempsChecked, primitiveFailedBB); +// jit.builder->SetInsertPoint(tempsChecked); // Storing values in the block's wrapping context for (uint32_t index = argCount - 1, count = argCount; count > 0; index--, count--) @@ -1764,10 +1972,13 @@ void MethodCompiler::compilePrimitive(TJITContext& jit, jit.builder->CreateCall3(m_baseFunctions.setObjectField, blockTemps, fieldIndex, argument); } - Value* const args[] = { block, jit.getCurrentContext() }; - Value* const result = jit.builder->CreateCall(m_runtimeAPI.invokeBlock, args); - - primitiveResult = result; + if (Function* const blockFunction = compileInferredBlock(jit)) { + // Direct call to the inferred block function + primitiveResult = jit.builder->CreateCall(blockFunction, block); + } else { + // Generic dispatch using runtime + primitiveResult = jit.builder->CreateCall2(m_runtimeAPI.invokeBlock, block, jit.getCurrentContext()); + } } break; case primitive::throwError: { //19 diff --git a/src/TauLinker.cpp b/src/TauLinker.cpp new file mode 100644 index 0000000..6b49736 --- /dev/null +++ b/src/TauLinker.cpp @@ -0,0 +1,459 @@ +#include +#include + +using namespace st; + +static const bool traces_enabled = false; + +struct TAssignSite { + InstructionNode* instruction; + bool byBackEdge; + + TAssignSite(InstructionNode* instruction, bool byBackEdge) + : instruction(instruction), byBackEdge(byBackEdge) {} +}; + +typedef std::vector TAssignSiteList; + +typedef std::pair TTauPair; +typedef std::set TTauPairSet; + +typedef std::map TRedundantTauMap; +TRedundantTauMap m_redundantTaus; + +typedef std::set TTauSet; +TTauSet m_processedTaus; + +class AssignLocator : public GraphWalker { +public: + AssignLocator( + TSmalltalkInstruction::TArgument argument, + const ControlGraph::TEdgeSet& backEdges, + const TauLinker::TClosureMap& closures + ) : argument(argument), backEdges(backEdges), closures(closures) {} + + virtual TVisitResult visitNode(st::ControlNode& node, const TPathNode* path) { + // TODO Phi node + + InstructionNode* const instruction = node.cast(); + if (!instruction) + return vrKeepWalking; + + //assert(instruction); + + switch (instruction->getInstruction().getOpcode()) { + case opcode::assignTemporary: + if (instruction->getInstruction().getArgument() == argument) { + const bool viaBackEdge = containsBackEdge(path); + + if (traces_enabled) { + std::printf("Found assign site: Node %.2u, back edge: %s\n", + instruction->getIndex(), + viaBackEdge ? "yes" : "no"); + } + + assignSites.push_back(TAssignSite(instruction, viaBackEdge)); + return vrSkipPath; + } + break; + + case opcode::sendBinary: + case opcode::sendMessage: { + TauLinker::TClosureMap::const_iterator iClosure = closures.find(instruction); + if (iClosure == closures.end()) + break; + + if (iClosure->second.writesIndex(argument)) { + const bool viaBackEdge = containsBackEdge(path); + + if (traces_enabled) { + std::printf("Found assigning closure: Node %.2u, back edge: %s\n", + instruction->getIndex(), + viaBackEdge ? "yes" : "no"); + } + + assignSites.push_back(TAssignSite(instruction, viaBackEdge)); + return vrSkipPath; + } + + break; + } + + default: + break; + } + + if (instruction->getInstruction().getOpcode() == opcode::assignTemporary) { + if (instruction->getInstruction().getArgument() == argument) { + const bool viaBackEdge = containsBackEdge(path); + + if (traces_enabled) { + std::printf("Found assign site: Node %.2u, back edge: %s\n", + instruction->getIndex(), + viaBackEdge ? "yes" : "no"); + } + + assignSites.push_back(TAssignSite(instruction, viaBackEdge)); + return vrSkipPath; + } + } + + return vrKeepWalking; + } + + bool containsBackEdge(const TPathNode* path) const { + // Search for back edges in the located path + + for (const TPathNode* p = path; p->prev; p = p->prev) { + const ControlGraph::TEdgeSet::const_iterator iEdge = backEdges.find( + st::BackEdgeDetector::TEdge( + static_cast(p->node), + static_cast(p->prev->node) + ) + ); + + if (iEdge != backEdges.end()) + return true; + } + + return false; + } + + const TSmalltalkInstruction::TArgument argument; + const ControlGraph::TEdgeSet& backEdges; + const TauLinker::TClosureMap& closures; + + TAssignSiteList assignSites; +}; + +void TauLinker::addClosureNode( + const st::InstructionNode& node, + const ClosureTauNode::TIndexList& readIndices, + const ClosureTauNode::TIndexList& writeIndices) +{ + TClosureInfo& closure = m_closures[&node]; + closure.readIndices = readIndices; + closure.writeIndices = writeIndices; +} + +st::GraphWalker::TVisitResult TauLinker::visitNode(st::ControlNode& node, const TPathNode* path) { + st::BackEdgeDetector::visitNode(node, path); + + if (InstructionNode* const instruction = node.cast()) { + switch (instruction->getInstruction().getOpcode()) { + case opcode::pushTemporary: + m_pendingNodes.insert(instruction); + break; + + case opcode::assignTemporary: + createType(*instruction); + break; + + case opcode::sendBinary: + case opcode::sendMessage: + m_pendingNodes.insert(instruction); + break; + + default: + break; + } + } + + return st::GraphWalker::vrKeepWalking; +} + +void TauLinker::nodesVisited() { + // Detected back edges + for (TEdgeSet::const_iterator iEdge = getBackEdges().begin(); iEdge != getBackEdges().end(); ++iEdge) { + if (traces_enabled) { + std::printf("Back edge: Node %.2u --> Node %.2u\n", + (*iEdge).from->getIndex(), + (*iEdge).to->getIndex() + ); + } + } + + getGraph().getMeta().hasLoops = !getBackEdges().empty(); + m_graph.getMeta().backEdges = getBackEdges(); + + // When all nodes visited, process the pending list + TInstructionSet::iterator iNode = m_pendingNodes.begin(); + for (; iNode != m_pendingNodes.end(); ++iNode) { + InstructionNode& node = **iNode; + + switch (node.getInstruction().getOpcode()) { + case opcode::pushTemporary: + processPushTemporary(node); + break; + + case opcode::sendBinary: + case opcode::sendMessage: + processClosure(node); + break; + + default: + break; + } + } + + optimizeTau(); +} + +void TauLinker::optimizeTau() { + detectRedundantTau(); + eraseRedundantTau(); +} + +void TauLinker::eraseRedundantTau() { + TRedundantTauMap::iterator iProvider = m_redundantTaus.begin(); + for (; iProvider != m_redundantTaus.end(); ++iProvider) { + if (traces_enabled) + printf("Now working on provider tau %.2u\n", (*iProvider).first->getIndex()); + + TTauPairSet& pendingTaus = iProvider->second; + TTauPairSet::iterator iPendingTau = pendingTaus.begin(); + + iPendingTau = pendingTaus.begin(); + for ( ; iPendingTau != pendingTaus.end(); ++iPendingTau) { + TauNode* const remainingTau = iPendingTau->first; + TauNode* const redundantTau = iPendingTau->second; + + if (m_processedTaus.find(remainingTau) != m_processedTaus.end()) { + if (traces_enabled) + printf("Tau %.2u was already processed earlier\n", remainingTau->getIndex()); + + continue; + } + + const TNodeSet& consumers = redundantTau->getConsumers(); + + // Remap all consumers to the remainingTau + TNodeSet::iterator iConsumer = consumers.begin(); + for ( ; iConsumer != consumers.end(); ++iConsumer) { + // FIXME Could there be non-instruction nodes? + if (InstructionNode* const instruction = (*iConsumer)->cast()) { + if (traces_enabled) { + printf("Remapping consumer %.2u from tau %.2u to remaining tau %.2u\n", + instruction->getIndex(), + redundantTau->getIndex(), + remainingTau->getIndex()); + } + + instruction->setTauNode(remainingTau); + remainingTau->addConsumer(instruction); + } + } + + // Remove all incomings of the redundantTau + TauNode::TIncomingMap::const_iterator iIncoming = redundantTau->getIncomingMap().begin(); + for ( ; iIncoming != redundantTau->getIncomingMap().end(); ++iIncoming) { + + if (traces_enabled) { + printf("Redundant tau %.2u is no longer consumer of %.2u\n", + redundantTau->getIndex(), + iIncoming->first->getIndex()); + } + + iIncoming->first->removeConsumer(redundantTau); + } + + // Marking tau as processed + m_processedTaus.insert(redundantTau); + + if (traces_enabled) + printf("Marking redundant tau %.2u as processed\n", redundantTau->getIndex()); + } + } + + m_redundantTaus.clear(); + + // Erasing all redundant taus completely + TTauSet::const_iterator iProcessedTau = m_processedTaus.begin(); + for (; iProcessedTau != m_processedTaus.end(); ++iProcessedTau) { + TauNode* const processedTau = *iProcessedTau; + + if (traces_enabled) + printf("Erasing processed tau %.2u\n", processedTau->getIndex()); + + //FIXME assert(processedTau->getIncomingMap().empty()); + getGraph().eraseNode(processedTau); + } + + m_processedTaus.clear(); +} + +void TauLinker::detectRedundantTau() { + TTauList::const_iterator iProvider = m_providers.begin(); + for (; iProvider != m_providers.end(); ++iProvider) { + const TNodeSet& consumers = (*iProvider)->getConsumers(); + if (consumers.size() < 2) + continue; + + if (traces_enabled) + printf("Looking for consumers of Tau %.2u (total %zu)\n", (*iProvider)->getIndex(), consumers.size()); + + TNodeSet::iterator iConsumer1 = consumers.begin(); + for ( ; iConsumer1 != consumers.end(); ++iConsumer1) { + TauNode* const tau1 = (*iConsumer1)->cast(); + if (! tau1 || tau1->getKind() == TauNode::tkClosure) + continue; + + TNodeSet::iterator iConsumer2 = iConsumer1; + ++iConsumer2; + + for (; iConsumer2 != consumers.end(); ++iConsumer2) { + TauNode* const tau2 = (*iConsumer2)->cast(); + if (!tau2 || tau2->getKind() == TauNode::tkClosure) + continue; + + if (tau1->getIncomingMap() == tau2->getIncomingMap()) { + if (traces_enabled) + printf("Tau %.2u and %.2u may be optimized\n", tau1->getIndex(), tau2->getIndex()); + + m_redundantTaus[*iProvider].insert(std::make_pair(tau1, tau2)); + } + } + } + } +} + +void TauLinker::createType(InstructionNode& instruction) { + if (instruction.getTauNode()) + return; + + TauNode* const tau = getGraph().newNode(); + tau->setKind(TauNode::tkProvider); + tau->addIncoming(&instruction); + instruction.setTauNode(tau); + + m_providers.push_back(tau); + + if (traces_enabled) { + std::printf("New type: Node %u.%.2u --> Tau %.2u, type %d\n", + instruction.getDomain()->getBasicBlock()->getOffset(), + instruction.getIndex(), + tau->getIndex(), + tau->getKind() + + ); + } +} + +void TauLinker::processPushTemporary(InstructionNode& instruction) { + if (instruction.getTauNode()) + return; + + // Searching for all AssignTemporary's and closures that provide a value for current node + AssignLocator locator(instruction.getInstruction().getArgument(), getBackEdges(), getClosures()); + locator.run(&instruction, GraphWalker::wdBackward, GraphWalker::wtDepthFirst, false); + + TauNode* aggregator = 0; + if (locator.assignSites.size() > 1) { + aggregator = m_graph.newNode(); + aggregator->setKind(TauNode::tkAggregator); + aggregator->addConsumer(&instruction); + instruction.setTauNode(aggregator); + } + + TAssignSiteList::const_iterator iAssignSite = locator.assignSites.begin(); + for (; iAssignSite != locator.assignSites.end(); ++iAssignSite) { + InstructionNode* const assignTemporary = (*iAssignSite).instruction->cast(); + assert(assignTemporary); + + TauNode* const assignType = assignTemporary->getTauNode(); + assert(assignType); + + if ((*iAssignSite).byBackEdge) + getGraph().getMeta().hasBackEdgeTau = true; + + if (aggregator) { + aggregator->addIncoming(assignType, (*iAssignSite).byBackEdge); + } else { + assignType->addConsumer(&instruction); + instruction.setTauNode(assignType); + } + + if (traces_enabled) { + std::printf("Tau: Node %.2u --> Tau %.2u, assign site %.2u is %s\n", + instruction.getIndex(), + aggregator ? aggregator->getIndex() : assignType->getIndex(), + assignTemporary->getIndex(), + (*iAssignSite).byBackEdge ? "below" : "above" + ); + } + + } +} + +void TauLinker::processClosure(InstructionNode& instruction) { + if (instruction.getTauNode()) + return; + + if (traces_enabled) + std::printf("Analyzing closure %.2u\n", instruction.getIndex()); + + TClosureMap::const_iterator iClosure = m_closures.find(&instruction); + if (iClosure == m_closures.end()) + return; + + const TClosureInfo& closure = iClosure->second; + + if (!closure.readIndices.size() && !closure.writeIndices.size()) + return; + + ClosureTauNode* const closureTau = m_graph.newNode(); + closureTau->setOrigin(&instruction); + closureTau->setKind(TauNode::tkClosure); + closureTau->addConsumer(&instruction); + instruction.setTauNode(closureTau); + + m_providers.push_back(closureTau); + + for (ClosureTauNode::TIndex index = 0; index < closure.readIndices.size(); index++) { + // Searching for all AssignTemporary's that provide a value for current node + AssignLocator locator(closure.readIndices[index], getBackEdges(), getClosures()); + locator.run(&instruction, GraphWalker::wdBackward, GraphWalker::wtDepthFirst, false); + + TauNode* aggregator = 0; + if (locator.assignSites.size() > 1) { + aggregator = m_graph.newNode(); + aggregator->setKind(TauNode::tkAggregator); + closureTau->addIncoming(aggregator); + } + + TAssignSiteList::const_iterator iAssignSite = locator.assignSites.begin(); + for (; iAssignSite != locator.assignSites.end(); ++iAssignSite) { + InstructionNode* const assignNode = (*iAssignSite).instruction->cast(); + assert(assignNode); + + TauNode* const assignTau = assignNode->getTauNode(); + assert(assignTau); + + if ((*iAssignSite).byBackEdge) + getGraph().getMeta().hasBackEdgeTau = true; + + if (aggregator) + aggregator->addIncoming(assignTau, (*iAssignSite).byBackEdge); + else + closureTau->addIncoming(assignTau); + + if (traces_enabled) { + std::printf("Tau %.2u <-- %s %.2u, assign site %.2u is %s\n", + assignTau->getIndex(), + aggregator ? "aggregator" : "closure", + aggregator ? aggregator->getIndex() : closureTau->getIndex(), + assignNode->getIndex(), + (*iAssignSite).byBackEdge ? "below" : "above" + ); + } + } + } +} + +void TauLinker::reset() { + m_graph.eraseTauNodes(); + m_providers.clear(); + m_pendingNodes.clear(); + m_closures.clear(); + resetStopNodes(); +} diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp new file mode 100644 index 0000000..be2e119 --- /dev/null +++ b/src/TypeAnalyzer.cpp @@ -0,0 +1,1507 @@ +#include +#include + +using namespace type; +using namespace st; + +static void printBlock(const Type& blockType, std::stringstream& stream) { + if (blockType.getSubTypes().size() < Type::bstCaptureIndex) { + stream << "(Block)"; + return; + } + + TMethod* const method = blockType[Type::bstOrigin].getValue()->cast(); + const uint16_t offset = TInteger(blockType[Type::bstOffset].getValue()); + + // Class>>method@offset#I[R][W]C + stream + << method->klass->name->toString() + << ">>" << method->name->toString() + << "@" << offset // block offset within a method + << "#" << blockType[Type::bstContextIndex].toString() // creating context index + << blockType[Type::bstReadsTemps].toString() // read temporaries indices + << blockType[Type::bstWritesTemps].toString(); // write temporaries indices + + if (blockType.getSubTypes().size() > Type::bstCaptureIndex) + stream << blockType[Type::bstCaptureIndex].toString(); // capture context index +} + +std::string Type::toString(bool subtypesOnly /*= true*/) const { + std::stringstream stream; + + switch (m_kind) { + case tkUndefined: return "?"; + case tkPolytype: return "*"; + + case tkLiteral: + if (isSmallInteger(getValue())) + stream << TInteger(getValue()).getValue(); + else if (getValue() == globals.nilObject) + return "nil"; + else if (getValue() == globals.trueObject) + return "true"; + else if (getValue() == globals.falseObject) + return "false"; + else if (getValue()->getClass() == globals.stringClass) + stream << "'" << getValue()->cast()->toString() << "'"; + else if (getValue()->getClass() == globals.badMethodSymbol->getClass()) + stream << "#" << getValue()->cast()->toString(); + else if (getValue()->getClass()->name->toString().find("Meta", 0, 4) != std::string::npos) + stream << getValue()->cast()->name->toString(); + else if (getValue()->getClass() == globals.stringClass->getClass()->getClass()) + stream << getValue()->cast()->name->toString(); + else + stream << "~" << getValue()->getClass()->name->toString(); + break; + + case tkMonotype: + if (getValue() == globals.blockClass) + printBlock(*this, stream); + else + stream << "(" << getValue()->cast()->name->toString() << ")"; + break; + + case tkArray: + if (!subtypesOnly) + stream << getValue()->cast()->name->toString(); + case tkComposite: { + stream << (m_kind == tkComposite ? "(" : "["); + + for (std::size_t index = 0; index < getSubTypes().size(); index++) { + if (index) + stream << ", "; + + stream << m_subTypes[index].toString(); + } + + stream << (m_kind == tkComposite ? ")" : "]"); + }; + } + + return stream.str(); +} + +Type Type::flatten() const { + if (m_kind != tkComposite) + return *this; + + TTypeSet typeSet; + flattenInto(typeSet); + + Type result(tkComposite); + for (TTypeSet::iterator iType = typeSet.begin(); iType != typeSet.end(); ++iType) + result.pushSubType(*iType); + + return result; +} + +void Type::flattenInto(TTypeSet& typeSet) const { + if (m_kind != tkComposite) { + typeSet.insert(*this); + return; + } + + for (std::size_t index = 0; index < m_subTypes.size(); index++) + m_subTypes[index].flattenInto(typeSet); +} + +void TypeAnalyzer::fillLinkerClosures() { + typedef InferContext::TBlockClosures::const_iterator TClosureIterator; + InferContext::TBlockClosures& closures = m_context.getBlockClosures(); + + for (TClosureIterator iClosure = closures.begin(); iClosure != closures.end(); ++iClosure) { + const InferContext::TSiteIndex siteIndex = iClosure->first; + InstructionNode* const instruction = m_siteMap[siteIndex]; + + if (!instruction) { + std::cout << "site index " << siteIndex << " is not found" << std::endl; + continue; + } + + Type& arguments = m_context[*instruction->getArgument()]; + + std::cout + << "analyzing potential closure args for " << instruction->getIndex() << " :: " + << arguments.toString() << std::endl; + + for (std::size_t argIndex = 0; argIndex < arguments.getSubTypes().size(); argIndex++) { + Type& blockType = arguments[argIndex]; + +// std::cout +// << "analyzing potential closure arg " << argIndex << " :: " +// << blockType.toString() << std::endl; + + if (blockType.getValue() != globals.blockClass || blockType.getKind() != Type::tkMonotype) + continue; // Not a block we may handle + + if (blockType.getSubTypes().empty()) + continue; // Non-literal or non-local block + + const Type& blockReadIndices = blockType[Type::bstReadsTemps]; + const Type& blockWrtieIndices = blockType[Type::bstWritesTemps]; + + ClosureTauNode::TIndexList readIndices; + ClosureTauNode::TIndexList wrtieIndices; + + readIndices.reserve(blockReadIndices.getSubTypes().size()); + wrtieIndices.reserve(blockWrtieIndices.getSubTypes().size()); + + for (std::size_t i = 0; i < blockReadIndices.getSubTypes().size(); i++) { + const std::size_t variableIndex = TInteger(blockReadIndices[i].getValue()); + readIndices.push_back(variableIndex); + } + + for (std::size_t i = 0; i < blockWrtieIndices.getSubTypes().size(); i++) { + const std::size_t variableIndex = TInteger(blockWrtieIndices[i].getValue()); + wrtieIndices.push_back(variableIndex); + } + + std::cout + << "adding closure node " << siteIndex + << " reads " << blockReadIndices.toString() + << " writes " << blockWrtieIndices.toString() + << std::endl; + + m_tauLinker.addClosureNode(*instruction, readIndices, wrtieIndices); + } + } +} + +bool TypeAnalyzer::basicRun() { + m_walker.resetStopNodes(); + m_walker.run(*m_graph.nodes_begin(), Walker::wdForward, GraphWalker::wtBreadthFirst, true); + + return m_context.getRawReturnType().getSubTypes().size() == 1; +} + +std::string TypeAnalyzer::getMethodName() { + TMethod* const method = m_graph.getParsedMethod()->getOrigin(); + std::ostringstream ss; + ss << method->klass->name->toString() + ">>" + method->name->toString(); + if (m_blockType) + ss << "@" << TInteger((*m_blockType)[Type::bstOffset].getValue()).getValue(); + + return ss.str(); +} + +void TypeAnalyzer::dumpTypes(const InferContext& context) { + TTypeMap::const_iterator iType = context.getTypes().begin(); + for (; iType != context.getTypes().end(); ++iType) + std::cout << iType->first << " " << iType->second.toString() << std::endl; + + std::cout << "Return type: " << context.getReturnType().toString() << std::endl; +} + +void TypeAnalyzer::run(const Type* blockType /*= 0*/) { + if (m_graph.isEmpty()) + return; + + m_blockType = blockType; + const std::string methodName = getMethodName(); + std::cout << "Initial pass on " << methodName << std::endl; + + m_baseRun = true; + m_literalBranch = true; + m_tauLinker.reset(); + m_tauLinker.run(); + bool singleReturn = basicRun(); + + if (m_literalBranch && singleReturn && !m_graph.getMeta().hasMutatingBlocks) + return; + + TTypeMap controlTypes; + +// for (std::size_t passNumber = 0; ; passNumber++) + std::size_t passNumber = 0; + { + if (m_graph.getMeta().hasLiteralBlocks) { + m_tauLinker.reset(); + fillLinkerClosures(); + m_tauLinker.run(); + m_context.resetTypes(); + + std::cout << "Base pass on " << methodName << " (" << passNumber << ")" << std::endl; + m_baseRun = true; + m_literalBranch = true; + singleReturn = basicRun(); + + if (m_literalBranch && singleReturn && !m_graph.getMeta().hasMutatingBlocks) + return; + } + + { + ControlGraphVisualizer vis(&m_graph, methodName, "dots/"); + vis.run(); + } + + std::cout << "Induction pass on " << methodName << " (" << passNumber << ")" << std::endl; + m_baseRun = false; + m_literalBranch = true; + //controlTypes = m_context.getTypes(); + singleReturn = basicRun(); + + //if (controlTypes == m_context.getTypes()) + //break; + } +} + +/*void TypeAnalyzer::run_(const Type* blockType) { + if (m_graph.isEmpty()) + return; + + m_blockType = blockType; + + // FIXME For correct inference we need to perform in-width traverse + + m_baseRun = true; + m_literalBranch = true; + m_walker.run(*m_graph.nodes_begin(), Walker::wdForward); + + std::cout << "Base run:" << std::endl; + type::TTypeList::const_iterator iType = m_context.getTypes().begin(); + for (; iType != m_context.getTypes().end(); ++iType) + std::cout << iType->first << " " << iType->second.toString() << std::endl; + + // If single return is detected, replace composite with it's subtype + // TODO We may need to store actual receiver class somewhere + Type& returnType = m_context.getReturnType(); + const bool singleReturn = (returnType.getSubTypes().size() == 1); + + if (singleReturn) + returnType = returnType[0]; + else + returnType.setKind(Type::tkComposite); + + std::cout << "return type: " << m_context.getReturnType().toString() << std::endl; + + if (m_literalBranch && singleReturn) { + // std::cout << "Return path was inferred literally. No need to perform induction run." << std::endl; + return; + } + + if (m_graph.getMeta().hasBackEdgeTau) { + m_baseRun = false; + + m_walker.resetStopNodes(); + m_walker.run(*m_graph.nodes_begin(), Walker::wdForward); + + std::cout << "Induction run:" << std::endl; + type::TTypeList::const_iterator iType = m_context.getTypes().begin(); + for (; iType != m_context.getTypes().end(); ++iType) + std::cout << iType->first << " " << iType->second.toString() << std::endl; + + if (returnType.getSubTypes().size() == 1) + returnType = returnType[0]; + else + returnType.setKind(Type::tkComposite); + + std::cout << "return type: " << m_context.getReturnType().toString() << std::endl; + } +}*/ + +Type& TypeAnalyzer::getArgumentType(const InstructionNode& instruction, std::size_t index /*= 0*/) { + ControlNode* const argNode = instruction.getArgument(index); + Type& result = m_context[*argNode]; + + if (PhiNode* const phi = argNode->cast()) + result = processPhi(*phi); + + return result; +} + +void TypeAnalyzer::processInstruction(InstructionNode& instruction) { +// std::printf("processing %.2u\n", instruction.getIndex()); + + switch (instruction.getInstruction().getOpcode()) { + case opcode::pushConstant: doPushConstant(instruction); break; + case opcode::pushLiteral: doPushLiteral(instruction); break; + case opcode::pushArgument: doPushArgument(instruction); break; + + case opcode::pushTemporary: doPushTemporary(instruction); break; + case opcode::assignTemporary: doAssignTemporary(instruction); break; + + case opcode::pushBlock: doPushBlock(instruction); break; + + case opcode::markArguments: doMarkArguments(instruction); break; + case opcode::sendUnary: doSendUnary(instruction); break; + case opcode::sendBinary: doSendBinary(instruction); break; + case opcode::sendMessage: doSendMessage(instruction); break; + + case opcode::doPrimitive: doPrimitive(instruction); break; + case opcode::doSpecial: doSpecial(instruction); break; + + default: + break; + } + +// std::printf("type of %.2u is %s\n", instruction.getIndex(), m_context[instruction].toString().c_str()); +} + +void TypeAnalyzer::doPushConstant(const InstructionNode& instruction) { + const TSmalltalkInstruction::TArgument argument = instruction.getInstruction().getArgument(); + Type& type = m_context[instruction]; + + switch (argument) { + case 0: case 1: case 2: case 3: case 4: + case 5: case 6: case 7: case 8: case 9: + type.set(TInteger(argument)); + break; + + case pushConstants::nil: type.set(globals.nilObject); break; + case pushConstants::trueObject: type.set(globals.trueObject); break; + case pushConstants::falseObject: type.set(globals.falseObject); break; + + default: + std::fprintf(stderr, "VM: unknown push constant %d\n", argument); + type.reset(); + } +} + +void TypeAnalyzer::doPushLiteral(const InstructionNode& instruction) { + TMethod* const method = m_graph.getParsedMethod()->getOrigin(); + const TSmalltalkInstruction::TArgument argument = instruction.getInstruction().getArgument(); + TObject* const literal = method->literals->getField(argument); + + m_context[instruction] = Type(literal); +} + +void TypeAnalyzer::doPushArgument(const InstructionNode& instruction) { + const TSmalltalkInstruction::TArgument index = instruction.getInstruction().getArgument(); + + if (m_blockType) { + if (InferContext* const methodContext = getMethodContext()) { + m_context[instruction] = methodContext->getArgument(index); + } + } else { + m_context[instruction] = m_context.getArgument(index); + } +} + +void TypeAnalyzer::doSendUnary(const InstructionNode& instruction) { + const Type& argType = m_context[*instruction.getArgument()]; + const unaryBuiltIns::Opcode opcode = static_cast(instruction.getInstruction().getArgument()); + + Type& result = m_context[instruction]; + switch (argType.getKind()) { + case Type::tkLiteral: + case Type::tkMonotype: + { + const bool isValueNil = + (argType.getValue() == globals.nilObject) + || (argType.getValue() == globals.nilObject->getClass()); + + if (opcode == unaryBuiltIns::isNil) + result.set(isValueNil ? globals.trueObject : globals.falseObject); + else + result.set(isValueNil ? globals.falseObject : globals.trueObject); + break; + } + + case Type::tkComposite: + case Type::tkArray: + { + // TODO Repeat the procedure over each subtype + result = Type(Type::tkPolytype); + break; + } + + default: + // * isNil -> (Boolean) + // * notNil -> (Boolean) + result.set(globals.trueObject->getClass()->parentClass); + } + +} + +void TypeAnalyzer::doSendBinary(InstructionNode& instruction) { + const Type& lhsType = m_context[*instruction.getArgument(0)]; + const Type& rhsType = m_context[*instruction.getArgument(1)]; + const binaryBuiltIns::Operator opcode = static_cast(instruction.getInstruction().getArgument()); + + Type& result = m_context[instruction]; + + if (isSmallInteger(lhsType.getValue()) && isSmallInteger(rhsType.getValue())) { + if (!m_baseRun) + return; + + const int32_t leftOperand = TInteger(lhsType.getValue()); + const int32_t rightOperand = TInteger(rhsType.getValue()); + + switch (opcode) { + case binaryBuiltIns::operatorLess: + result.set((leftOperand < rightOperand) ? globals.trueObject : globals.falseObject); + break; + + case binaryBuiltIns::operatorLessOrEq: + result.set((leftOperand <= rightOperand) ? globals.trueObject : globals.falseObject); + break; + + case binaryBuiltIns::operatorPlus: + result.set(TInteger(leftOperand + rightOperand)); + break; + + default: + std::fprintf(stderr, "VM: Invalid opcode %d passed to sendBinary\n", opcode); + } + + return; + } + + // Literal int or (SmallInt) monotype + const bool isInt1 = isSmallInteger(lhsType.getValue()) || lhsType.getValue() == globals.smallIntClass; + const bool isInt2 = isSmallInteger(rhsType.getValue()) || rhsType.getValue() == globals.smallIntClass; + + if (isInt1 && isInt2) { + switch (opcode) { + case binaryBuiltIns::operatorLess: + case binaryBuiltIns::operatorLessOrEq: + // (SmallInt) <= (SmallInt) -> (Boolean) + result.set(globals.trueObject->getClass()->parentClass); + break; + + case binaryBuiltIns::operatorPlus: + // (SmallInt) + (SmallInt) -> (SmallInt) + result.set(globals.smallIntClass); + break; + + default: + std::fprintf(stderr, "VM: Invalid opcode %d passed to sendBinary\n", opcode); + result.reset(); // ? + } + + return; + } + + // In case of complex invocation encode resursive analysis of operator as a message + TSymbol* const selector = globals.binaryMessages[opcode]->cast(); + + Type arguments(Type::tkArray); + arguments.pushSubType(lhsType); + arguments.pushSubType(rhsType); + + captureContext(instruction, arguments); + + if (InferContext* const context = m_system.inferMessage(selector, arguments, &m_contextStack)) { + result = context->getReturnType(); + m_context.getReferredContexts().insert(context); + + if (result == Type(Type::tkComposite)) + m_walker.addStopNode(*instruction.getOutEdges().begin()); + } else { + result = Type(Type::tkPolytype); + } +} + +void TypeAnalyzer::doMarkArguments(const InstructionNode& instruction) { + Type& result = m_context[instruction]; + + if (!m_baseRun) + result.reset(); + + for (std::size_t index = 0; index < instruction.getArgumentsCount(); index++) { + ControlNode* const argument = instruction.getArgument(index); + TTypeMap::const_iterator iType = m_context.getTypes().find(argument->getIndex()); + + if (iType == m_context.getTypes().end()) { + if (PhiNode* const phi = argument->cast()) + processPhi(*phi); + + const Type& type = m_context[*argument]; + result.pushSubType(type); + + } else { + result.pushSubType(iType->second); + } + } + + result.set(globals.arrayClass, Type::tkArray); +} + +InferContext* TypeAnalyzer::getMethodContext() { + assert(m_blockType); + + for (TContextStack* stack = &m_contextStack; stack; stack = stack->parent) { + const TInteger contextIndex((*m_blockType)[Type::bstContextIndex].getValue()); + + if (stack->context.getIndex() == static_cast(contextIndex)) + return &stack->context; + } + + return 0; +} + +void TypeAnalyzer::doPushTemporary(const InstructionNode& instruction) { + if (TauNode* const tau = instruction.getTauNode()) { + switch (tau->getKind()) { + case TauNode::tkAggregator: + processTau(*tau); + case TauNode::tkProvider: + m_context[instruction] = m_context[*tau]; + break; + + case TauNode::tkUnknown: + std::fprintf(stderr, "Unknown tau node index %u found from %u\n", tau->getIndex(), instruction.getIndex()); + assert(false); + break; + + case TauNode::tkClosure: { + const ClosureTauNode* const closureTau = tau->cast(); + const uint32_t closureIndex = closureTau->getOrigin()->getIndex(); + const uint16_t tempIndex = instruction.getInstruction().getArgument(); + + m_context[instruction] = m_context.getBlockClosures()[closureIndex][tempIndex]; + break; + } + } + } else if (m_blockType) { + const uint16_t argIndex = TInteger((*m_blockType)[Type::bstArgIndex].getValue()); + const uint16_t tempIndex = instruction.getInstruction().getArgument(); + + if (tempIndex >= argIndex) { + m_context[instruction] = m_context.getArgument(tempIndex - argIndex); + } else { + if (InferContext* const methodContext = getMethodContext()) { + const TInteger captureIndex((*m_blockType)[Type::bstCaptureIndex].getValue()); + InferContext::TVariableMap& closureTypes = methodContext->getBlockClosures()[captureIndex]; + + m_context[instruction] = closureTypes[tempIndex]; + } else { + std::cout << "Could not find method context for block " << m_blockType->toString() << std::endl; + } + } + } else { + // Method variables are initialized to nil by default + m_context[instruction] = Type(globals.nilObject); + } +} + +void TypeAnalyzer::doAssignTemporary(const InstructionNode& instruction) { + const TauNode* const tau = instruction.getTauNode(); + assert(tau); + assert(tau->getKind() == TauNode::tkProvider); + + const Type& argumentType = getArgumentType(instruction); + m_context[*tau] = argumentType; + + if (!m_blockType) + return; + + InferContext* const methodContext = getMethodContext(); + if (!methodContext) { + std::cout << "method context not found for " << instruction.getIndex() << std::endl; + return; + } + + const TInteger captureIndex((*m_blockType)[Type::bstCaptureIndex].getValue()); + + InferContext::TVariableMap& closureTypes = methodContext->getBlockClosures()[captureIndex]; + const uint16_t tempIndex = instruction.getInstruction().getArgument(); + + InferContext::TVariableMap::iterator iType = closureTypes.find(tempIndex); + if (iType == closureTypes.end()) + closureTypes[tempIndex] = argumentType; + else + closureTypes[tempIndex] &= argumentType; + + std::cout + << "doAssignTemporary, writing temporary " << tempIndex + << " in closure " << captureIndex.getValue() + << " type & " << argumentType.toString() + << " = " << closureTypes[tempIndex].toString() + << std::endl; +} + +void TypeAnalyzer::doPushBlock(const InstructionNode& instruction) { + if (const PushBlockNode* const pushBlock = instruction.cast()) { + TMethod* const origin = pushBlock->getParsedBlock()->getContainer()->getOrigin(); + const uint16_t offset = pushBlock->getParsedBlock()->getStartOffset(); + const uint16_t argIndex = instruction.getInstruction().getArgument(); + + Type& blockType = m_context[instruction]; + + if (!blockType.getSubTypes().empty()) + return; // Already processed before + + m_graph.getMeta().hasLiteralBlocks = true; + + blockType.set(globals.blockClass, Type::tkMonotype); + blockType.pushSubType(origin); // [Type::bstOrigin] + blockType.pushSubType(Type(TInteger(offset))); // [Type::bstOffset] + blockType.pushSubType(Type(TInteger(argIndex))); // [Type::bstArgIndex] + + if (m_blockType) + blockType.pushSubType((*m_blockType)[Type::bstContextIndex]); + else + blockType.pushSubType(Type(TInteger(m_context.getIndex()))); // [Type::bstContextIndex] + + ControlGraph* const blockGraph = m_system.getBlockGraph(pushBlock->getParsedBlock()); + assert(blockGraph); + + typedef ControlGraph::TMetaInfo::TIndexList TIndexList; + const TIndexList& readsTemporaries = blockGraph->getMeta().readsTemporaries; + const TIndexList& writesTemporaries = blockGraph->getMeta().writesTemporaries; + + if (writesTemporaries.size()) + m_graph.getMeta().hasMutatingBlocks = true; + + Type readIndices(globals.arrayClass, Type::tkArray); + Type writeIndices(globals.arrayClass, Type::tkArray); + + for (std::size_t index = 0; index < readsTemporaries.size(); index++) { + // We're interested only in temporaries from lexical context, not block arguments + if (readsTemporaries[index] >= argIndex) + continue; + + readIndices.pushSubType(Type(TInteger(readsTemporaries[index]))); + } + + for (std::size_t index = 0; index < writesTemporaries.size(); index++) { + // We're interested only in temporaries from lexical context, not block arguments + if (writesTemporaries[index] >= argIndex) + continue; + + writeIndices.pushSubType(Type(TInteger(writesTemporaries[index]))); + } + + blockType.pushSubType(readIndices); // [Type::bstReadsTemps] + blockType.pushSubType(writeIndices); // [Type::bstWritesTemps] + } +} + +class TypeLocator : public GraphWalker { +public: + TypeLocator(std::size_t tempIndex, const ControlGraph::TEdgeSet& backEdges, InferContext& context, bool noBackEdges) + : m_tempIndex(tempIndex), m_backEdges(backEdges), m_context(context), m_noBackEdges(noBackEdges), m_firstResult(true) {} + + const Type& getResult() const { return m_result; } + +private: + virtual TVisitResult visitNode(st::ControlNode& node, const TPathNode* path) { + if (InstructionNode* const instruction = node.cast()) { + switch (instruction->getInstruction().getOpcode()) { + case opcode::assignTemporary: + return checkAssignTemporary(*instruction, path); + + case opcode::sendBinary: + case opcode::sendMessage: + return checkClosure(*instruction, path); + + default: + break; + } + } + + return vrKeepWalking; + } + + bool containsBackEdge(const TPathNode* path) const { + // Search for back edges in the located path + + for (const TPathNode* p = path; p->prev; p = p->prev) { + const ControlGraph::TEdgeSet::const_iterator iEdge = m_backEdges.find( + st::BackEdgeDetector::TEdge( + static_cast(p->node), + static_cast(p->prev->node) + ) + ); + + if (iEdge != m_backEdges.end()) + return true; + } + + return false; + } + + TVisitResult checkAssignTemporary(InstructionNode& instruction, const TPathNode* path) { + if (instruction.getInstruction().getArgument() != m_tempIndex) + return vrKeepWalking; + + TauNode* const tau = instruction.getTauNode(); + if (! tau) + return vrKeepWalking; + + // Searching for back edges in the located path + const bool viaBackEdge = containsBackEdge(path); + + std::printf("TypeLocator : Found assign site: Node %.2u :: %s, back edge: %s\n", + instruction.getIndex(), + m_context[*tau].toString().c_str(), + viaBackEdge ? "yes" : "no"); + + if (viaBackEdge && m_noBackEdges) + return vrSkipPath; + + if (m_firstResult) { + m_result = m_context[*tau]; + m_firstResult = false; + } else { + m_result &= m_context[*tau]; + } + + return vrSkipPath; + } + + TVisitResult checkClosure(InstructionNode& instruction, const TPathNode* path) { + std::cout << "checkClosure " << instruction.getIndex() << std::endl; + + const bool viaBackEdge = containsBackEdge(path); + + if (viaBackEdge && m_noBackEdges) + return vrSkipPath; + + const InferContext::TBlockClosures& closures = m_context.getBlockClosures(); + InferContext::TBlockClosures::const_iterator iClosure = closures.find(instruction.getIndex()); + + if (iClosure == closures.end()) { + std::cout << "checkClosure " << instruction.getIndex() << " is not found" << std::endl; + return vrKeepWalking; + } + + // FIXME Currently we link to a closure even if it is not mutating temporaries + + const InferContext::TVariableMap& closureVariables = iClosure->second; + InferContext::TVariableMap::const_iterator iType = closureVariables.find(m_tempIndex); + + if (iType == closureVariables.end()) { + std::cout << "checkClosure " << instruction.getIndex() << " no variable" << std::endl; + return vrKeepWalking; + } + + std::printf("TypeLocator : Found closure site: Node %.2u :: %s, back edge: %s\n", + instruction.getIndex(), + iType->second.toString().c_str(), + viaBackEdge ? "yes" : "no"); + + if (m_firstResult) { + m_result = iType->second; + m_firstResult = false; + } else { + m_result &= iType->second; + } + + return vrSkipPath; + } + +private: + const TSmalltalkInstruction::TArgument m_tempIndex; + const ControlGraph::TEdgeSet& m_backEdges; + InferContext& m_context; + const bool m_noBackEdges; + + Type m_result; + bool m_firstResult; +}; + +void TypeAnalyzer::captureContext(InstructionNode& instruction, Type& arguments) { + TMethod* const currentMethod = m_graph.getParsedMethod()->getOrigin(); + +// std::cout +// << "captureContext of " << instruction.getIndex() +// << ": Analyzing capture arguments " +// << arguments.toString() << std::endl; + + // We interested in literal blocks with context info from the same method + for (std::size_t argIndex = 0; argIndex < arguments.getSubTypes().size(); argIndex++) { + Type& blockType = arguments[argIndex]; + + if (blockType.getValue() != globals.blockClass || blockType.getKind() != Type::tkMonotype) + continue; // Not a block we may handle + + if (blockType.getSubTypes().empty() || blockType[Type::bstOrigin].getValue() != currentMethod) + continue; // Non-literal or non-local block + + if (TInteger(blockType[Type::bstContextIndex].getValue()) != static_cast(m_context.getIndex())) + continue; // Block was created in another method (non-local) + + std::cout + << "captureContext of " << instruction.getIndex() + << " : Analyzing block " << blockType.toString() << std::endl; + + // Index of the capture site + const InferContext::TSiteIndex siteIndex = instruction.getIndex(); + //FIXME if (blockType[Type::bstReadsTemps].getSubTypes().size()) + { + std::cout << "writing siteIndex: " << siteIndex << std::endl; + blockType.pushSubType(Type(TInteger(siteIndex))); // [Type::bstCaptureIndex] + + std::cout << "writing " << &instruction << " as site index " << siteIndex << std::endl; + m_siteMap[siteIndex] = &instruction; + } + + // Indexes of temporaries from the lexical context. See TypeAnalyzer::pushBlock() + const Type& readIndices = blockType[Type::bstReadsTemps]; + + // Prepare captured context by writing inferred variable types at the capture point + for (std::size_t i = 0; i < readIndices.getSubTypes().size(); i++) { + const std::size_t variableIndex = TInteger(readIndices[i].getValue()); + + // TODO Move out of the loop + TypeLocator locator( + variableIndex, // look for temporary with this index + m_graph.getMeta().backEdges, + m_context, + m_baseRun // base run = skip back edges + ); + + // Detect types of temporaries accessible from the current call site + locator.run(&instruction, st::GraphWalker::wdBackward, GraphWalker::wtDepthFirst, false); + + InferContext::TVariableMap& typeMap = m_context.getBlockClosures()[siteIndex]; + InferContext::TVariableMap::iterator iType = typeMap.find(variableIndex); + + if (iType != typeMap.end()) + iType->second &= locator.getResult(); + else + typeMap[variableIndex] = locator.getResult(); + + } + } +} + +void TypeAnalyzer::doSendMessage(InstructionNode& instruction, bool sendToSuper /*= false*/) { + TSymbolArray& literals = *m_graph.getParsedMethod()->getOrigin()->literals; + const uint32_t literalIndex = sendToSuper ? + instruction.getInstruction().getExtra() : + instruction.getInstruction().getArgument(); + + TSymbol* const selector = literals[literalIndex]; + Type& arguments = m_context[*instruction.getArgument()]; + + captureContext(instruction, arguments); + + Type& result = m_context[instruction]; + if (InferContext* const context = m_system.inferMessage(selector, arguments, &m_contextStack, sendToSuper)) { + result = context->getReturnType(); + m_context.getReferredContexts().insert(context); + + if (result == Type(Type::tkComposite)) + m_walker.addStopNode(*instruction.getOutEdges().begin()); + } else { + result = Type(Type::tkPolytype); + } +} + +void TypeAnalyzer::doPrimitive(const InstructionNode& instruction) { + const uint8_t opcode = instruction.getInstruction().getExtra(); + + Type primitiveResult; + + switch (opcode) { + case primitive::throwError: + m_walker.addStopNode(*instruction.getOutEdges().begin()); + return; + + case primitive::objectsAreEqual: { + Type& object1 = m_context[*instruction.getArgument(0)]; + Type& object2 = m_context[*instruction.getArgument(1)]; + + if (object1.getKind() == Type::tkLiteral && object2.getKind() == Type::tkLiteral) + primitiveResult = Type(object1.getValue() == object2.getValue() ? globals.trueObject : globals.falseObject); + else + primitiveResult = Type(globals.trueObject->getClass()->parentClass, Type::tkMonotype); + + break; + } + + case primitive::allocateObject: + case primitive::allocateByteArray: + { + const Type& klassType = m_context[*instruction.getArgument()]; + + // instance <- Class new + // + // <7 Array 2> -> Array[nil, nil] + // <7 Object 2> -> Object[nil, nil] + // <7 Object (SmallInt)> -> (Object) + // <7 Object *> -> (Object) + // <7 (Class) 2> -> *[nil, nil] -> * + // <7 * 2> -> *[nil, nil] -> * + // <7 * (SmallInt)> -> * + // <7 * *> -> * + + switch (klassType.getKind()) { + case Type::tkLiteral: + // If we literally know the class we may define the instance's type + primitiveResult = Type(klassType.getValue(), Type::tkMonotype); + break; + + default: + // Otherwise it's completely unknown what will be the instance's type + primitiveResult = Type(Type::tkPolytype); + } + + break; + } + + case primitive::getClass: { + const Type& selfType = m_context[*instruction.getArgument()]; + TObject* const self = selfType.getValue(); + + switch (selfType.getKind()) { + case Type::tkLiteral: { + // Here class itself is a literal, not a monotype + TClass* selfClass = isSmallInteger(self) ? globals.smallIntClass : self->getClass(); + primitiveResult = Type(selfClass, Type::tkLiteral); + break; + } + + case Type::tkMonotype: + // (Object) class -> Object + primitiveResult = Type(self); + break; + + default: { + // String -> MetaString -> Class + // String class class = Class + // TClass* const classClass = globals.stringClass->getClass()->getClass(); + // primitiveResult = Type(classClass, Type::tkMonotype); + primitiveResult = Type(Type::tkPolytype); + } + } + + break; + } + + case primitive::getSize: { + const Type& self = m_context[*instruction.getArgument(0)]; + + if (self.getKind() == Type::tkLiteral) { + TObject* const value = self.getValue(); + const std::size_t size = isSmallInteger(value) ? 0 : value->getSize(); + + primitiveResult = Type(TInteger(size)); + } else { + primitiveResult = Type(globals.smallIntClass); + } + + // TODO What about Monotype and TCLass::instanceSize? + // Will not work for binary objects. + + break; + } + + case primitive::arrayAt: + case primitive::bulkReplace: + primitiveResult = Type(Type::tkPolytype); + break; + + // FIXME actually this is primitive for ByteArray>>basicAt: + case primitive::stringAt: + primitiveResult = Type(globals.smallIntClass, Type::tkMonotype); + break; + + case primitive::arrayAtPut: + case primitive::stringAtPut: + primitiveResult = m_context[*instruction.getArgument(1)]; + break; + + case primitive::cloneByteObject: + primitiveResult = m_context[*instruction.getArgument(1)]; + break; + + case primitive::blockInvoke: { + Type& block = m_context[*instruction.getArgument(0)]; + Type arguments(Type::tkArray); + + if (instruction.getArgumentsCount() > 1) + arguments.pushSubType(m_context[*instruction.getArgument(1)]); + + if (instruction.getArgumentsCount() > 2) + arguments.pushSubType(m_context[*instruction.getArgument(2)]); + + if (InferContext* const blockContext = m_system.inferBlock(block, arguments, &m_contextStack)) { + primitiveResult = blockContext->getReturnType(); + m_context.getReferredContexts().insert(blockContext); + } else { + primitiveResult = Type(Type::tkPolytype); + } + + break; + } + + case primitive::smallIntAdd: + case primitive::smallIntSub: + case primitive::smallIntMul: + case primitive::smallIntDiv: + case primitive::smallIntMod: + case primitive::smallIntEqual: + case primitive::smallIntLess: + { + const Type& self = m_context[*instruction.getArgument(0)].fold(); + const Type& arg = m_context[*instruction.getArgument(1)].fold(); + static const Type smallInt(globals.smallIntClass, Type::tkMonotype); + + if (isSmallInteger(self.getValue()) && isSmallInteger(arg.getValue())) { + const int lhs = TInteger(self.getValue()).getValue(); + const int rhs = TInteger(arg.getValue()).getValue(); + + switch (opcode) { + case primitive::smallIntAdd: primitiveResult = Type(TInteger(lhs + rhs)); break; + case primitive::smallIntSub: primitiveResult = Type(TInteger(lhs - rhs)); break; + case primitive::smallIntMul: primitiveResult = Type(TInteger(lhs * rhs)); break; + case primitive::smallIntDiv: primitiveResult = Type(TInteger(lhs / rhs)); break; + case primitive::smallIntMod: primitiveResult = Type(TInteger(lhs % rhs)); break; + + case primitive::smallIntEqual: + primitiveResult = (lhs == rhs) ? globals.trueObject : globals.falseObject; + break; + + case primitive::smallIntLess: + primitiveResult = (lhs < rhs) ? globals.trueObject : globals.falseObject; + break; + } + } else if ((self & smallInt) == smallInt && (arg & smallInt) == smallInt) { + primitiveResult = smallInt; + } else { + primitiveResult = Type(Type::tkPolytype); + } + + break; + } + } + + m_context.getRawReturnType().addSubType(primitiveResult); + + // TODO This should depend on the primitive inference outcome + m_walker.addStopNode(*instruction.getOutEdges().begin()); +} + +void TypeAnalyzer::doSpecial(InstructionNode& instruction) { + const TSmalltalkInstruction::TArgument argument = instruction.getInstruction().getArgument(); + + switch (argument) { + case special::branchIfFalse: + case special::branchIfTrue: { + const bool branchIfTrue = (argument == special::branchIfTrue); + const Type& argType = m_context[*instruction.getArgument()]; + + const BranchNode* const branch = instruction.cast(); + assert(branch); + + if (argType.getValue() == globals.trueObject || argType.getValue() == globals.trueObject->getClass()) + m_walker.addStopNode(branchIfTrue ? branch->getSkipNode() : branch->getTargetNode()); + else if (argType.getValue() == globals.falseObject || argType.getValue() == globals.falseObject->getClass()) + m_walker.addStopNode(branchIfTrue ? branch->getTargetNode() : branch->getSkipNode()); + else + m_literalBranch = false; + + break; + } + + case special::stackReturn: + m_context.getRawReturnType().addSubType(getArgumentType(instruction)); + break; + + case special::selfReturn: + m_context.getRawReturnType().addSubType(m_context.getArgument(0)); + break; + + case special::blockReturn: + getMethodContext()->getRawReturnType().addSubType(getArgumentType(instruction)); + break; + + case special::sendToSuper: + doSendMessage(instruction, true); + break; + + case special::duplicate: + m_context[instruction] = m_context[*instruction.getArgument()]; + break; + } +} + +Type& TypeAnalyzer::processPhi(const PhiNode& phi) { + Type& result = m_context[phi]; + bool typeAssigned = false; + + const TNodeSet& incomings = phi.getRealValues(); + TNodeSet::iterator iNode = incomings.begin(); + for (; iNode != incomings.end(); ++iNode) { + // FIXME We need to track the source of the phi's incoming. + // We may ignore tkUndefined only if node lies on the dead path. + + const InstructionNode* const incoming = (*iNode)->cast(); + const TauNode* const incomingTau = incoming->getTauNode(); + const Type& incomingType = incomingTau ? m_context[*incomingTau] : m_context[*incoming]; + + if (! typeAssigned) { + typeAssigned = true; + result = incomingType; + } else { + std::cout << "phi: " << result.toString() << " | " << incomingType.toString(); + result |= incomingType; + std::cout << " = " << result.toString() << std::endl; + } + } + + if (result.getSubTypes().size() == 1) + result = result[0]; + else + result = result.flatten(); + + return result; +} + +void TypeAnalyzer::processTau(const TauNode& tau) { + Type& result = m_context[tau]; + + const TauNode::TIncomingMap& incomings = tau.getIncomingMap(); + TauNode::TIncomingMap::const_iterator iNode = incomings.begin(); + + bool typeAssigned = false; + + for (; iNode != incomings.end(); ++iNode) { + const bool byBackEdge = iNode->second; + if (byBackEdge && m_baseRun) + continue; + + std::cout << "Adding subtype to " << tau.getIndex() << " : " << m_context[*iNode->first].toString() << std::endl; + + if (! typeAssigned) { + result = m_context[*iNode->first]; + typeAssigned = true; + } else { + result &= m_context[*iNode->first]; + } + } + + std::cout << "Tau value is " << result.toString() << std::endl; +} + +void TypeAnalyzer::walkComplete() { +// std::printf("walk complete\n"); +} + +ControlGraph* TypeSystem::getMethodGraph(TMethod* method) { + TGraphCache::iterator iGraph = m_graphCache.find(method); + if (iGraph != m_graphCache.end()) + return iGraph->second.second; + + ParsedMethod* const parsedMethod = new ParsedMethod(method); + ControlGraph* const controlGraph = new ControlGraph(parsedMethod); + controlGraph->buildGraph(); + + TGraphEntry& entry = m_graphCache[method]; + entry.first = parsedMethod; + entry.second = controlGraph; + +// { +// std::ostringstream ss; +// ss << method->klass->name->toString() + ">>" + method->name->toString(); +// +// ControlGraphVisualizer vis(controlGraph, ss.str(), "dots/"); +// vis.run(); +// } + + return controlGraph; +} + +ControlGraph* TypeSystem::getBlockGraph(st::ParsedBlock* parsedBlock) { + TBlockGraphCache::const_iterator iGraph = m_blockGraphCache.find(parsedBlock); + if (iGraph != m_blockGraphCache.end()) + return iGraph->second; + + ControlGraph* const graph = new ControlGraph(parsedBlock->getContainer(), parsedBlock); + graph->buildGraph(); + + m_blockGraphCache[parsedBlock] = graph; + + return graph; +} + +InferContext* TypeSystem::inferMessage( + TSelector selector, + const Type& arguments, + TContextStack* parent, + bool sendToSuper /*= false*/) +{ + if (!selector || arguments.getKind() != Type::tkArray || arguments.getSubTypes().empty()) + return 0; + + const Type& self = arguments[0]; + + switch (self.getKind()) { + case Type::tkUndefined: + case Type::tkPolytype: + return 0; + + // TODO Handle the case when self is a composite type + case Type::tkComposite: + return 0; + + case Type::tkLiteral: + case Type::tkMonotype: + case Type::tkArray: + break; + } + + TContextMap& contextMap = m_contextCache[selector]; + + Type key(Type::tkArray); + key.pushSubType(arguments); + key.pushSubType(sendToSuper ? globals.trueObject : globals.falseObject); + + { + const TContextMap::iterator iContext = contextMap.find(key); + if (iContext != contextMap.end()) { + InferContext* const cachedContext = iContext->second; + std::printf("*** Found cached context for key: %s, selector: %s\n", key.toString().c_str(), selector->toString().c_str()); + + if (cachedContext->getRecursionKind() == InferContext::rkUnknown) { + for (TContextStack* stack = parent; stack; stack = stack->parent) { + if (stack->context.getIndex() == cachedContext->getIndex()) { + std::printf("*** Context %s::%s>>%s recursively calls itself!\n", + cachedContext->getArguments().toString().c_str(), + cachedContext->getMethod()->klass->name->toString().c_str(), + selector->toString().c_str()); + + cachedContext->setRecursionKind(InferContext::rkYes); + return cachedContext; + } + } + + cachedContext->setRecursionKind(InferContext::rkNo); + } + + return cachedContext; + } + + std::printf("*** Not found cached context for key: %s, selector: %s\n", key.toString().c_str(), selector->toString().c_str()); + } + + TClass* receiver = 0; + + if (self.getKind() == Type::tkLiteral) { + if (isSmallInteger(self.getValue())) + receiver = globals.smallIntClass; + else if (self.getValue()->getClass() == globals.stringClass->getClass()->getClass()) + receiver = self.getValue()->cast(); + else + receiver = self.getValue()->getClass(); + } else { + receiver = self.getValue()->cast(); + } + + TMethod* const method = m_vm.lookupMethod(selector, sendToSuper ? receiver->parentClass : receiver); + + if (! method) { // TODO Redirect to #doesNotUnderstand: statically + std::printf("Lookup failed for %s::?>>%s...\n", + arguments.toString().c_str(), + selector->toString().c_str()); + + return 0; + } + + InferContext* const inferContext = new InferContext(method, m_lastContextIndex++, arguments); + contextMap[key] = inferContext; + + std::printf("(%u) Analyzing %s::%s>>%s ...\n%s!\n", + inferContext->getIndex(), + arguments.toString().c_str(), + method->klass->name->toString().c_str(), + selector->toString().c_str(), + method->text->toString().c_str()); + + ControlGraph* const methodGraph = getMethodGraph(method); + assert(methodGraph); + + TContextStack contextStack(*inferContext, parent); + type::TypeAnalyzer analyzer(*this, *methodGraph, contextStack); + analyzer.run(); + + inferContext->getRawReturnType() = inferContext->getRawReturnType().flatten(); + const Type& returnType = inferContext->getReturnType(); + + std::printf("%s::%s>>%s -> %s\n", + arguments.toString().c_str(), + method->klass->name->toString().c_str(), + selector->toString().c_str(), + returnType.toString().c_str()); + + return inferContext; +} + +InferContext* TypeSystem::inferDynamicBlock( + Type& block, + const Type& arguments, + const Type& temporaries, + TContextStack* parent) +{ + if (! block.isBlock()) + return 0; + + Type key(Type::tkArray); + key.pushSubType(block); + key.pushSubType(arguments); + key.pushSubType(temporaries); + + TBlockCache::iterator iBlock = m_blockCache.find(key); + if (iBlock != m_blockCache.end()) + return iBlock->second; + + TMethod* const method = block[Type::bstOrigin].getValue()->cast(); + InferContext* const inferContext = new InferContext(method, m_lastContextIndex++, arguments); + m_blockCache[key] = inferContext; + std::printf("Cached dynamic block context %s -> %p (index %u), cache size %u\n", + key.toString().c_str(), inferContext, inferContext->getIndex(), m_blockCache.size()); + + doInferBlock(inferContext, block, arguments, parent); + + return inferContext; +} + +InferContext* TypeSystem::inferBlock(Type& block, const Type& arguments, TContextStack* parent) { + if (! block.isBlock()) + return 0; + + Type key(Type::tkArray); + key.pushSubType(block); + key.pushSubType(arguments); + + TBlockCache::iterator iBlock = m_blockCache.find(key); + if (iBlock != m_blockCache.end()) + return iBlock->second; + + TMethod* const method = block[Type::bstOrigin].getValue()->cast(); + InferContext* const inferContext = new InferContext(method, m_lastContextIndex++, arguments); + + m_blockCache[key] = inferContext; + std::printf("Cached block context %s -> %p (index %u), cache size %u\n", + key.toString().c_str(), inferContext, inferContext->getIndex(), m_blockCache.size()); + + doInferBlock(inferContext, block, arguments, parent); + return inferContext; + +} + +void TypeSystem::doInferBlock(InferContext* inferContext, Type& block, const Type& arguments, TContextStack* parent) { + TMethod* const method = block[Type::bstOrigin].getValue()->cast(); + const uint16_t offset = TInteger(block[Type::bstOffset].getValue()); + + ControlGraph* const methodGraph = getMethodGraph(method); + assert(methodGraph); + + std::printf("(%u) Analyzing block %s::%s ...\n", + inferContext->getIndex(), + arguments.toString().c_str(), + block.toString().c_str()); + + st::ParsedMethod* const parsedMethod = methodGraph->getParsedMethod(); + st::ParsedBlock* const parsedBlock = parsedMethod->getParsedBlockByOffset(offset); + + ControlGraph* const blockGraph = getBlockGraph(parsedBlock); + assert(blockGraph); + + { + std::ostringstream ss; + ss << method->klass->name->toString() << ">>" << method->name->toString() << "@" << offset; + + ControlGraphVisualizer vis(blockGraph, ss.str(), "dots/"); + vis.run(); + } + + TContextStack contextStack(*inferContext, parent); + type::TypeAnalyzer analyzer(*this, *blockGraph, contextStack); + analyzer.run(&block); + + inferContext->getRawReturnType() = inferContext->getRawReturnType().flatten(); + + std::printf("%s::%s -> %s\n", arguments.toString().c_str(), block.toString().c_str(), + inferContext->getRawReturnType().toString().c_str()); +} + +void type::TypeSystem::dumpAllContexts() const { + std::cout << "Full context dump: " << std::endl; + + TContextCache::const_iterator iSelectorMap = m_contextCache.begin(); + for (; iSelectorMap != m_contextCache.end(); ++iSelectorMap) { + const TSymbol* const selector = iSelectorMap->first; + + TContextMap::const_iterator iContext = iSelectorMap->second.begin(); + for (; iContext != iSelectorMap->second.end(); ++iContext) { + const Type& arguments = iContext->first; + const InferContext* const context = iContext->second; + + std::printf("%s::%s>>%s -> %s\n", + arguments.toString().c_str(), + context->getMethod()->klass->name->toString().c_str(), + selector->toString().c_str(), + context->getReturnType().toString().c_str()); + } + } +} + +void type::TypeSystem::drawCallGraph() const { + std::ofstream stream; + stream.open("dots/callgraph.dot", std::ios::out | std::ios::trunc); + + stream << "digraph G2 {\n rankdir=LR;\n"; + + typedef std::set TIndexSet; + TIndexSet parsedContexts; + + TContextCache::const_iterator iSelectorMap = m_contextCache.begin(); + for (; iSelectorMap != m_contextCache.end(); ++iSelectorMap) { + const TSymbol* const selector = iSelectorMap->first; + + TContextMap::const_iterator iContext = iSelectorMap->second.begin(); + for (; iContext != iSelectorMap->second.end(); ++iContext) { + const Type& arguments = iContext->first[0]; + const bool sendToSuper = iContext->first[1].getValue() == globals.trueObject; + const InferContext* const context = iContext->second; + + stream + << context->getIndex() << " [shape=\"box\" color=\"" + << (sendToSuper ? "blue" : "black") + << "\" label=\"" + << arguments.toString() << "::" + << context->getMethod()->klass->name->toString() << ">>" + << selector->toString().c_str() << "\n" + << "Index: " << context->getIndex() << ", Returns: " + << context->getReturnType().toString() << "\"];\n"; + + const InferContext::TContextSet& referers = context->getReferredContexts(); + InferContext::TContextSet::const_iterator iReferer = referers.begin(); + for (; iReferer != referers.end(); ++iReferer) { + stream << context->getIndex() << " -> " << (*iReferer)->getIndex() << ";\n"; + + } + } + } + + TBlockCache::const_iterator iBlock = m_blockCache.begin(); + for (; iBlock != m_blockCache.end(); ++iBlock) { + const Type& block = iBlock->first[0]; + const Type& arguments = iBlock->first[1]; + InferContext* blockContext = iBlock->second; + + // TODO Block return + stream + << blockContext->getIndex() << " [shape=\"box\" color=\"red\" label=\"" + << arguments.toString() << "::" + << block.toString() << "\n" + << "Index: " << blockContext->getIndex() << ", Returns: " + << blockContext->getReturnType().toString() << "\"];\n"; + + const InferContext::TContextSet& referers = blockContext->getReferredContexts(); + InferContext::TContextSet::const_iterator iReferer = referers.begin(); + for (; iReferer != referers.end(); ++iReferer) + stream << blockContext->getIndex() << " -> " << (*iReferer)->getIndex() << ";\n"; + + } + + stream << "}\n"; + stream.close(); +} + +type::InferContext* type::TypeSystem::findBlockContext(std::size_t contextIndex) const +{ + TBlockCache::const_iterator iContext = m_blockCache.begin(); + for (; iContext != m_blockCache.end(); ++iContext) { + InferContext* const candidate = iContext->second; + if (candidate->getIndex() == contextIndex) + return candidate; + } + + return 0; +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 98a7cde..d3d9ecb 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -5,6 +5,9 @@ add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure) macro(cxx_test pretty_name bin_name sources libs) add_executable(${bin_name} ${sources}) target_link_libraries(${bin_name} supc++ -pthread ${libs} ${GTEST_BOTH_LIBRARIES} ${READLINE_LIBS_TO_LINK}) + if (USE_LLVM) + target_link_libraries(${bin_name} jit trampoline ${LLVM_LIBS} ${LLVM_LD_FLAGS}) + endif() set_target_properties(${bin_name} PROPERTIES COMPILE_DEFINITIONS TESTS_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}/\") add_test(${pretty_name} ${bin_name}) add_dependencies(check ${bin_name}) @@ -22,3 +25,4 @@ cxx_test(StackSemantics test_stack_semantics "${CMAKE_CURRENT_SOURCE_DIR}/stack_ # TODO cxx_test(StackUnderflow test_stack_underflow "${CMAKE_CURRENT_SOURCE_DIR}/stack_underflow.cpp" "stapi") cxx_test(DecodeAllMethods test_decode_all_methods "${CMAKE_CURRENT_SOURCE_DIR}/decode_all_methods.cpp" "stapi;memory_managers;standard_set") cxx_test("VM::primitives" test_vm_primitives "${CMAKE_CURRENT_SOURCE_DIR}/vm_primitives.cpp" "memory_managers;standard_set") +cxx_test(Inference test_inference "${CMAKE_CURRENT_SOURCE_DIR}/inference.cpp" "stapi;memory_managers;standard_set") diff --git a/tests/data/Inference.image b/tests/data/Inference.image new file mode 100755 index 0000000..f97bf11 Binary files /dev/null and b/tests/data/Inference.image differ diff --git a/tests/helpers/VMImage.h b/tests/helpers/VMImage.h index 26fb851..0386d57 100644 --- a/tests/helpers/VMImage.h +++ b/tests/helpers/VMImage.h @@ -11,11 +11,12 @@ class H_VMImage std::auto_ptr m_memoryManager; std::auto_ptr m_smalltalkImage; public: - H_VMImage(const std::string& imageName) { - std::auto_ptr memoryManager(new BakerMemoryManager()); - memoryManager->initializeHeap(1024*1024, 1024*1024); - std::auto_ptr smalltalkImage(new Image(memoryManager.get())); - smalltalkImage->loadImage(TESTS_DIR "./data/" + imageName + ".image"); + H_VMImage(const std::string& imageName) : + m_memoryManager(new BakerMemoryManager()), + m_smalltalkImage(new Image(m_memoryManager.get())) + { + m_memoryManager->initializeHeap(1024*1024, 1024*1024); + m_smalltalkImage->loadImage(TESTS_DIR "./data/" + imageName + ".image"); } TObjectArray* newArray(std::size_t fields); TString* newString(std::size_t size); diff --git a/tests/inference.cpp b/tests/inference.cpp new file mode 100644 index 0000000..62a4536 --- /dev/null +++ b/tests/inference.cpp @@ -0,0 +1,541 @@ +#include +#include "patterns/InitVMImage.h" +#include + +class P_Inference : public P_InitVM_Image +{ +public: + type::TypeSystem* m_type_system; + + P_Inference() : P_InitVM_Image(), m_type_system(0) {} + virtual ~P_Inference() {} + + virtual void SetUp() + { + P_InitVM_Image::SetUp(); + m_type_system = new type::TypeSystem(*m_vm); + } + virtual void TearDown() { + P_InitVM_Image::TearDown(); + delete m_type_system; + } + + type::InferContext* inferMessage( + TClass* const objectClass, + const std::string& methodName, + const type::Type& args, + type::TContextStack* parent, + bool sendToSuper = false + ) { + EXPECT_NE(static_cast(0), objectClass) << "Class is null"; + TMethod* const method = objectClass->methods->find(methodName.c_str()); + EXPECT_NE(static_cast(0), method) << "Method is null"; + + TSymbol* const selector = method->name; + return m_type_system->inferMessage(selector, args, parent, sendToSuper); + } + + type::InferContext* inferMessage( + const std::string& className, + const std::string& methodName, + const type::Type& args, + type::TContextStack* parent, + bool sendToSuper = false + ) { + TClass* const objectClass = m_image->getGlobal(className.c_str()); + return this->inferMessage(objectClass, methodName, args, parent, sendToSuper); + } + +}; + +INSTANTIATE_TEST_CASE_P(_, P_Inference, ::testing::Values(std::string("Inference")) ); + +template +std::vector convert_array_to_vector(const T (&source_array)[N]) { + return std::vector(source_array, source_array+N); +} + +TEST_P(P_Inference, new) +{ + std::string types_array[] = {"Array", "List", "True", "False", "Dictionary"}; + std::vector types = convert_array_to_vector<>(types_array); + + for(std::vector::const_iterator it = types.begin(); it != types.end(); ++it) { + const std::string& type = *it; + SCOPED_TRACE(type); + TClass* const klass = m_image->getGlobal(type.c_str()); + ASSERT_TRUE(klass != 0) << "could not find class for " << type; + + TClass* const metaClass = klass->getClass(); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(klass, type::Type::tkLiteral)); + + type::InferContext* const inferContext = this->inferMessage(metaClass, "new", args, 0, true); + ASSERT_EQ( type::Type(klass), inferContext->getReturnType() ); + } +} + +TEST_P(P_Inference, Object__isNil) +{ + { + SCOPED_TRACE("nil"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.nilObject)); + + type::InferContext* const inferContext = this->inferMessage("Object", "isNil", args, 0); + ASSERT_EQ( type::Type(globals.trueObject), inferContext->getReturnType() ); + } + { + SCOPED_TRACE("not nil"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.trueObject)); + + type::InferContext* const inferContext = this->inferMessage("Object", "isNil", args, 0); + ASSERT_EQ( type::Type(globals.falseObject), inferContext->getReturnType() ); + } +} + +TEST_P(P_Inference, Object__notNil) +{ + { + SCOPED_TRACE("nil"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.nilObject)); + + type::InferContext* const inferContext = this->inferMessage("Object", "notNil", args, 0); + ASSERT_EQ( type::Type(globals.falseObject), inferContext->getReturnType() ); + } + { + SCOPED_TRACE("not nil"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.trueObject)); + + type::InferContext* const inferContext = this->inferMessage("Object", "notNil", args, 0); + ASSERT_EQ( type::Type(globals.trueObject), inferContext->getReturnType() ); + } +} + +TEST_P(P_Inference, Object__class) +{ + TClass* classes_array[] = {globals.smallIntClass, globals.stringClass, globals.arrayClass, globals.blockClass}; + std::vector classes = convert_array_to_vector<>(classes_array); + + for(std::vector::const_iterator it = classes.begin(); it != classes.end(); ++it) { + TClass* klass = *it; + SCOPED_TRACE(klass->name->toString()); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(klass)); + + type::InferContext* const inferContext = this->inferMessage("Object", "class", args, 0); + ASSERT_EQ( type::Type(klass, type::Type::tkLiteral), inferContext->getReturnType() ) ; + } +} + +TEST_P(P_Inference, Object__isMemberOf) +{ + { + SCOPED_TRACE("42 isMemberOf: SmallInt"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(TInteger(42))); + args.pushSubType(type::Type(globals.smallIntClass, type::Type::tkLiteral)); + + type::InferContext* const inferContext = this->inferMessage("Object", "isMemberOf:", args, 0); + ASSERT_EQ( type::Type(globals.trueObject), inferContext->getReturnType() ); + } + { + SCOPED_TRACE("42 isMemberOf: (SmallInt)"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(TInteger(42))); + args.pushSubType(type::Type(globals.smallIntClass)); + + type::InferContext* const inferContext = this->inferMessage("Object", "isMemberOf:", args, 0); + ASSERT_EQ( type::Type(m_image->getGlobal("Boolean")), inferContext->getReturnType() ); + } +} + +TEST_P(P_Inference, Object__isKindOf) +{ + { + SCOPED_TRACE("42 isKindOf: SmallInt"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(TInteger(42))); + args.pushSubType(type::Type(globals.smallIntClass, type::Type::tkLiteral)); + + type::InferContext* const inferContext = this->inferMessage("Object", "isKindOf:", args, 0); + ASSERT_EQ( type::Type(globals.trueObject), inferContext->getReturnType() ); + } + { + SCOPED_TRACE("42 isKindOf: Number"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(TInteger(42))); + args.pushSubType(type::Type(globals.smallIntClass)); + + type::InferContext* const inferContext = this->inferMessage("Object", "isKindOf:", args, 0); + ASSERT_EQ( type::Type(m_image->getGlobal("Boolean")), inferContext->getReturnType().fold() ); + } +} + +TEST_P(P_Inference, Object__respondsTo) +{ + { + SCOPED_TRACE("42 respondsTo: #<"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(TInteger(42))); + args.pushSubType(type::Type(globals.binaryMessages[0])); + + type::InferContext* const inferContext = this->inferMessage("Object", "respondsTo:", args, 0); + ASSERT_EQ( type::Type(type::Type::tkPolytype), inferContext->getReturnType() ); // FIXME + } +} + +TEST_P(P_Inference, Collection__includes) +{ + { + SCOPED_TRACE("Array new includes: 42"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.arrayClass)); + args.pushSubType(type::Type(TInteger(42))); + + type::InferContext* const inferContext = this->inferMessage("Collection", "includes:", args, 0); + ASSERT_EQ( type::Type(m_image->getGlobal("Boolean")), inferContext->getReturnType().fold() ); + } +} + +TEST_P(P_Inference, OrderedArray) +{ + { + SCOPED_TRACE("OrderedArray new location: 42"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(m_image->getGlobal("OrderedArray"))); + args.pushSubType(type::Type(TInteger(42))); + + type::InferContext* const inferContext = this->inferMessage("OrderedArray", "location:", args, 0); + ASSERT_EQ( type::Type(globals.smallIntClass), inferContext->getReturnType().fold() ); + } +} + +TEST_P(P_Inference, True) +{ + { + SCOPED_TRACE("True>>not"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.trueObject)); + + type::InferContext* const inferContext = this->inferMessage("True", "not", args, 0); + ASSERT_EQ( type::Type(globals.falseObject), inferContext->getReturnType() ); + } + { + SCOPED_TRACE("True>>and:"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.trueObject)); + args.pushSubType(type::Type(globals.blockClass)); + + type::InferContext* const inferContext = this->inferMessage("True", "and:", args, 0); + ASSERT_EQ( type::Type(m_image->getGlobal("Boolean")), inferContext->getReturnType().fold() ); + } + { + SCOPED_TRACE("True>>or:"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.trueObject)); + args.pushSubType(type::Type(globals.blockClass)); + + type::InferContext* const inferContext = this->inferMessage("True", "or:", args, 0); + ASSERT_EQ( type::Type(globals.trueObject), inferContext->getReturnType() ); + } +} + +TEST_P(P_Inference, False) +{ + { + SCOPED_TRACE("False>>not"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.falseObject)); + + type::InferContext* const inferContext = this->inferMessage("False", "not", args, 0); + ASSERT_EQ( type::Type(globals.trueObject), inferContext->getReturnType() ); + } + { + SCOPED_TRACE("False>>and:"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.falseObject)); + args.pushSubType(type::Type(globals.blockClass)); + + type::InferContext* const inferContext = this->inferMessage("False", "and:", args, 0); + ASSERT_EQ( type::Type(globals.falseObject), inferContext->getReturnType() ); + } + { + SCOPED_TRACE("False>>or:"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.falseObject)); + args.pushSubType(type::Type(globals.blockClass)); + + type::InferContext* const inferContext = this->inferMessage("False", "or:", args, 0); + ASSERT_EQ( type::Type(m_image->getGlobal("Boolean")), inferContext->getReturnType().fold() ); + } +} + +TEST_P(P_Inference, Boolean) +{ + { + SCOPED_TRACE("not"); + { + SCOPED_TRACE("False"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.falseObject)); + + type::InferContext* const inferContext = this->inferMessage("Boolean", "not", args, 0, true); + ASSERT_EQ( type::Type(globals.trueObject), inferContext->getReturnType() ); + } + { + SCOPED_TRACE("False"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.trueObject)); + + type::InferContext* const inferContext = this->inferMessage("Boolean", "not", args, 0, true); + ASSERT_EQ( type::Type(globals.falseObject), inferContext->getReturnType() ); + } + } + { + SCOPED_TRACE("and:"); + { + SCOPED_TRACE("False + block"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.falseObject)); + args.pushSubType(type::Type(globals.blockClass)); + + type::InferContext* const inferContext = this->inferMessage("Boolean", "and:", args, 0, true); + ASSERT_EQ( type::Type(globals.falseObject), inferContext->getReturnType() ); + } + { + SCOPED_TRACE("True + block"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.trueObject)); + args.pushSubType(type::Type(globals.blockClass)); + + type::InferContext* const inferContext = this->inferMessage("Boolean", "and:", args, 0, true); + ASSERT_EQ( type::Type(m_image->getGlobal("Boolean")), inferContext->getReturnType().fold() ); + } + } + { + SCOPED_TRACE("or:"); + { + SCOPED_TRACE("False + block"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.falseObject)); + args.pushSubType(type::Type(globals.blockClass)); + + type::InferContext* const inferContext = this->inferMessage("Boolean", "or:", args, 0, true); + ASSERT_EQ( type::Type(m_image->getGlobal("Boolean")), inferContext->getReturnType().fold() ); + } + { + SCOPED_TRACE("True + block"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.trueObject)); + args.pushSubType(type::Type(globals.blockClass)); + + type::InferContext* const inferContext = this->inferMessage("Boolean", "or:", args, 0, true); + ASSERT_EQ( type::Type(globals.trueObject), inferContext->getReturnType() ); + } + } +} + +TEST_P(P_Inference, SmallInt) +{ + { + SCOPED_TRACE("SmallInt>>asSmallInt"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(TInteger(42))); + + type::InferContext* const inferContext = this->inferMessage("SmallInt", "asSmallInt", args, 0); + ASSERT_EQ( type::Type(TInteger(42)), inferContext->getReturnType() ); + } + { + SCOPED_TRACE("SmallInt>>+ SmallInt"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(TInteger(40))); + args.pushSubType(type::Type(TInteger(2))); + + type::InferContext* const inferContext = this->inferMessage("SmallInt", "+", args, 0); + ASSERT_EQ( type::Type(TInteger(42)), inferContext->getReturnType() ); + } +} + +TEST_P(P_Inference, Number) +{ + { + SCOPED_TRACE("Number::new"); + type::Type args(globals.arrayClass, type::Type::tkArray); + TClass* const metaNumberClass = m_image->getGlobal("Number")->getClass(); + args.pushSubType(type::Type(metaNumberClass)); + + type::InferContext* const inferContext = this->inferMessage(metaNumberClass, "new", args, 0); + ASSERT_EQ( type::Type(TInteger(0)), inferContext->getReturnType() ); + } + { + SCOPED_TRACE("Number>>factorial"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(TInteger(4))); + + type::InferContext* const inferContext = this->inferMessage("Number", "factorial", args, 0); + ASSERT_EQ( type::Type(TInteger(24)), inferContext->getReturnType() ); + } + { + SCOPED_TRACE("Number>>negative"); + { + SCOPED_TRACE("-SmallInt"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(TInteger(-1))); + + type::InferContext* const inferContext = this->inferMessage("Number", "negative", args, 0); + ASSERT_EQ( type::Type(globals.trueObject), inferContext->getReturnType() ); + } + { + SCOPED_TRACE("+SmallInt"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(TInteger(1))); + + type::InferContext* const inferContext = this->inferMessage("Number", "negative", args, 0); + ASSERT_EQ( type::Type(globals.falseObject), inferContext->getReturnType() ); + } + } + { + SCOPED_TRACE("Number>>negated"); + { + SCOPED_TRACE("-SmallInt"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(TInteger(-1))); + + type::InferContext* const inferContext = this->inferMessage("Number", "negated", args, 0); + ASSERT_EQ( type::Type(TInteger(1)), inferContext->getReturnType() ); + } + { + SCOPED_TRACE("+SmallInt"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(TInteger(1))); + + type::InferContext* const inferContext = this->inferMessage("Number", "negated", args, 0); + ASSERT_EQ( type::Type(TInteger(-1)), inferContext->getReturnType() ); + } + } + { + SCOPED_TRACE("Number>>absolute"); + { + SCOPED_TRACE("-SmallInt"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(TInteger(-1))); + + type::InferContext* const inferContext = this->inferMessage("Number", "absolute", args, 0); + ASSERT_EQ( type::Type(TInteger(1)), inferContext->getReturnType() ); + } + { + SCOPED_TRACE("+SmallInt"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(TInteger(1))); + + type::InferContext* const inferContext = this->inferMessage("Number", "absolute", args, 0); + ASSERT_EQ( type::Type(TInteger(1)), inferContext->getReturnType() ); + } + } + { + SCOPED_TRACE("Number>>to:do:"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(TInteger(1))); + args.pushSubType(type::Type(TInteger(100))); + args.pushSubType(type::Type(globals.blockClass)); + + type::InferContext* const inferContext = this->inferMessage("Number", "to:do:", args, 0); + ASSERT_EQ( type::Type(TInteger(1)), inferContext->getReturnType() ); + } +} + +TEST_P(P_Inference, String) +{ + { + SCOPED_TRACE("String>>words"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.stringClass)); + + type::InferContext* const inferContext = this->inferMessage("String", "words", args, 0); + ASSERT_EQ( type::Type(m_image->getGlobal("List")), inferContext->getReturnType() ); + } +} + +TEST_P(P_Inference, Array) +{ + { + SCOPED_TRACE("Array>>sort:"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.arrayClass)); + args.pushSubType(type::Type(globals.blockClass)); + + type::InferContext* const inferContext = this->inferMessage("Array", "sort:", args, 0); + ASSERT_EQ( type::Type(globals.arrayClass), inferContext->getReturnType() ); + } + { + SCOPED_TRACE("Array>>size"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.arrayClass)); + + type::InferContext* const inferContext = this->inferMessage("Array", "size", args, 0); + ASSERT_EQ( type::Type(globals.smallIntClass), inferContext->getReturnType() ); + } +} + +TEST_P(P_Inference, Char) +{ + TClass* const charClass = m_image->getGlobal("Char"); + ASSERT_TRUE(charClass != 0) << "could not find class for Char"; + TClass* const metaCharClass = charClass->getClass(); + + { + SCOPED_TRACE("Char::basicNew:"); + + type::Type argsBasicNew(globals.arrayClass, type::Type::tkArray); + argsBasicNew.pushSubType(type::Type(charClass, type::Type::tkLiteral)); + argsBasicNew.pushSubType(type::Type(TInteger(33))); + + type::InferContext* const inferBasicNewContext = this->inferMessage(metaCharClass, "basicNew:", argsBasicNew, 0); + ASSERT_EQ( type::Type(charClass), inferBasicNewContext->getReturnType() ); + + /*{ + SCOPED_TRACE("Char>>value"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(inferBasicNewContext->getReturnType())); + + type::InferContext* const inferContext = this->inferMessage(charClass, "value", args, 0); + ASSERT_EQ( type::Type(globals.smallIntClass), inferContext->getReturnType() ); + }*/ + } + { + SCOPED_TRACE("Char::new:"); + + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(charClass, type::Type::tkLiteral)); + args.pushSubType(type::Type(TInteger(42))); + + type::InferContext* const inferBasicNewContext = this->inferMessage(metaCharClass, "new:", args, 0); + ASSERT_EQ( type::Type(type::Type::tkPolytype), inferBasicNewContext->getReturnType() ); + } +} + +TEST_P(P_Inference, DISABLED_Includes) +{ + { + SCOPED_TRACE("Dictionary new includes: #asd"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(m_image->getGlobal("Dictionary"))); + args.pushSubType(type::Type(m_image->getGlobal("Symbol"))); + + type::InferContext* const inferContext = this->inferMessage("Collection", "includes:", args, 0); + ASSERT_EQ( type::Type(m_image->getGlobal("Boolean")), inferContext->getReturnType().fold() ); + } + { + SCOPED_TRACE("OrderedArray new includes: 42"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(m_image->getGlobal("OrderedArray"))); + args.pushSubType(type::Type(TInteger(42))); + + type::InferContext* const inferContext = this->inferMessage("Collection", "includes:", args, 0); + ASSERT_EQ( type::Type(m_image->getGlobal("Boolean")), inferContext->getReturnType().fold() ); + } +} diff --git a/tests/patterns/InitVMImage.h b/tests/patterns/InitVMImage.h index 7206014..bef2b7f 100644 --- a/tests/patterns/InitVMImage.h +++ b/tests/patterns/InitVMImage.h @@ -2,13 +2,16 @@ #define LLST_PATTERN_INIT_VM_IMAGE_INCLUDED #include -#include "../helpers/VMImage.h" +#include class P_InitVM_Image : public ::testing::TestWithParam { protected: - H_VMImage* m_image; + std::auto_ptr m_memoryManager; public: + std::auto_ptr m_image; + std::auto_ptr m_vm; + P_InitVM_Image() : m_memoryManager(), m_image(), m_vm() {} virtual ~P_InitVM_Image() {} virtual void SetUp() @@ -19,10 +22,21 @@ class P_InitVM_Image : public ::testing::TestWithParaminitializeHeap(1024*1024, 1024*1024); + m_image.reset(new Image(m_memoryManager.get())); + m_image->loadImage(TESTS_DIR "./data/" + image_name + ".image"); + m_vm.reset(new SmalltalkVM(m_image.get(), m_memoryManager.get())); } virtual void TearDown() { - delete m_image; + m_vm.reset(); + m_image.reset(); + m_memoryManager.reset(); } }; diff --git a/tests/vm_primitives.cpp b/tests/vm_primitives.cpp index 40092a5..c797053 100644 --- a/tests/vm_primitives.cpp +++ b/tests/vm_primitives.cpp @@ -1,9 +1,30 @@ #include #include "helpers/VMImage.h" -#include "patterns/InitVMImage.h" #include #include +class P_InitVM_Image : public ::testing::TestWithParam +{ +protected: + H_VMImage* m_image; +public: + virtual ~P_InitVM_Image() {} + + virtual void SetUp() + { + bool is_parameterized_test = ::testing::UnitTest::GetInstance()->current_test_info()->value_param(); + if (!is_parameterized_test) { + // Use TEST_P instead of TEST_F ! + abort(); + } + ParamType image_name = GetParam(); + m_image = new H_VMImage(image_name); + } + virtual void TearDown() { + delete m_image; + } +}; + INSTANTIATE_TEST_CASE_P(_, P_InitVM_Image, ::testing::Values(std::string("VMPrimitives")) ); TEST_P(P_InitVM_Image, smallint)