A máquina SECD Pedro Vasconcelos 20 de Fevereiro de 2014
O que é a máquina SECD? Um interpretador da linguagem funcional ISWIM (Landin, 1964) Máquina virtual para compilação LISP/Scheme (Henderson, 1980) Utilizada em implementações reais (LispMe no Palm Pilot) Desenhada para linguagens call-by-value Pode ser modificada para lazy evaluation (embora existam alternativas mais eficientes)
Máquina abstracta ou virtual? A SECD original interpreta directamente termos de sintaxe abstracta (máquina abstracta) Vamos apresentar uma máquina que interpreta pseudo-instruções (máquina virtual) Omitimos: 1 estruturas de dados (listas, tuplos, etc.); 2 escolha de representações concretas em memória; 3 tradução das pseudo-instruções para código-máquina real; 4 ambiente necessário para execução: alocação de memória, garbage collection, I/O...
Bibliografia 1 Capítulo 6 de Functional Programming: Application and Implementation, Henderson, 1980, Prentice-Hall International. 2 Capítulo 7 de The Architecture of Symbolic Computers, Kogge, 1991, McGraw-Hill International.
SECD: Stack, Environment, Control & Dump A configuração da máquina é um quinteto s, e, c, d, m s pilha de valores temporários (stack); e pilha de valores das variáveis livres (environment); c sequência de instruções (control); d pilha de continuações (dump); m memória (closures).
Resolução de nomes Durante compilação vamos associar nomes de variáveis a índices no ambiente. Interpretador termo: x + y ambiente: [x 23, y 42] Compilador termo: x + y tabela de símbolos: [x 0, y 1] código gerado: [LD 0, LD 1, ADD] ambiente: [23, 42] } compilação } execução
Notação de De Bruijn Identifica as variáveis pela profundidade do ligador λ: λx. (λy. y x) x λ (λ 1 2) 1 Os ambiente passam a ser apenas listas de valores: [v 1, v 2,..., v i,..., v n ] Cada variável é associada a um índice i.
Closures Valores funcionais são representados por closures, e.g. (λy. x + y, [x 2] ) }{{}}{{} λ-termo ambiente Na máquina SECD os λ-termos são traduzido para código compilado: Closure = (Code, Env)
Closures Representamos a memória como uma função parcial que associa endereços a closures: Store = Addr Closure A função next dá o próximo endereço livre: next :: Store Addr
Pilha de temporários Os operandos e resultado de instruções são passados na pilha de temporários. A pilha é uma lista de valores: Stack = [Value] v : vs [ ] pilha vazia v topo da pilha, vs resto da pilha Os valores são inteiros ou endereços de closures: v Value = n Int a Addr
Pilha de continuações A pilha de continuações é uma lista de trios (s, e, c): Dump = [(Stack, Env, Code)] Guarda temporariamente os registos da máquina durante a chamada de funções.
Conjunto de pseudo-instruções LD n load variable LDC n load constant LDF c load function LDRF c load recursive function AP apply RTN return SEL c c select zero/non-zero JOIN join main control ADD add SUB subtract MUL multiply HALT halt execution Nota: a SECD descrita no livro de Henderson tem mais instruções.
Exemplos de compilação 1 + (2 3) [LDC 1, LDC 2, LDC 3, MUL, ADD] λx. x + 1 λx. ifzero x 1 0 [LDF [LD 0, LDC 1, ADD, RTN]] [LDF [LD 0, SEL [LDC 1, JOIN] [LDC 0, JOIN], RTN]]
Compilação e execução de instruções O compilador é uma função compile :: Term Symtable Code A tabela de símbolos é uma lista; cada identificador é associado ao seu índice na lista. Symtable = [Ident] Cada instrução é definida por uma transição de estado: s, e, c, d, m }{{} configuração actual s, e, c, d, m }{{} configuração seguinte
Variáveis, constantes e operações aritméticas compile n sym = [LDC n] compile x sym = [LD i] onde i = elemindex x sym compile (e 1 + e 2 ) sym = compile e 1 sym ++ compile e 2 sym ++ [ADD] compile (e 1 e 2 ) sym = compile e 1 sym ++ compile e 2 sym ++ [SUB]. etc.
Execução s, e, (LD i) : c, d, m v i : s, e, c, d, m, onde e = [v 0, v 1,..., v i,...] s, e, (LDC n) : c, d, m n : s, e, c, d, m v 2 : v 1 : s, e, ADD : c, d, m (v 1 + v 2 ) : s, e, c, d, m v 2 : v 1 : s, e, SUB : c, d, m (v 1 v 2 ) : s, e, c, d, m v 2 : v 1 : s, e, MUL : c, d, m (v 1 v 2 ) : s, e, c, d, m
Abstração e aplicação λx. e 1 Constrói uma nova closure; 2 Deixa o endereço do resultado na pilha. (e 1 e 2 ) 1 Avalia e 1 (obtém uma closure); 2 Avalia e 2 (obtém o valor do argumento); 3 Guarda o contexto de execução na dump; 4 Executa o código da closure; 5 Recupera o contexto de execução.
Compilação compile (λx. e) sym = [LDF (compile e sym ++ [RTN])] onde sym = extend sym x compile (e 1 e 2 ) sym = compile e 1 sym ++ compile e 2 sym ++ [AP]
Execução s, e, (LDF c ) : c, d, m a : s, e, c, d, m[a (c, e)] onde a = next m v : a : s, e, AP : c, d, m [ ], v : e, c, (s, e, c) : d, m se m(a) = (c, e ) v : s, e, RTN : c, (s, e, c ) : d, m v : s, e, c, d, m
Condicional ifzero e 0 e 1 e 2 1 Avalia e 0 (resultado deve ser um inteiro); 2 Guarda o contexto de execução na dump 3 Se topo da pilha é 0 avalia e 1 ; caso contrário, avalia e 2 ; 4 Recupera o contexto de execução guardado.
Compilação compile (if e 0 e 1 e 2 ) sym = compile e 0 sym ++ [SEL c 1 c 2 ] onde c 1 = compile e 1 sym ++ [JOIN] c 2 = compile e 2 sym ++ [JOIN]
Execução 0 : s, e, (SEL c 1 c 2 ) : c, d, m s, e, c 1, ([], [], c) : d, m v : s, e, (SEL c 1 c 2 ) : c, d, m s, e, c 2, ([], [], c) : d, m se v Int v 0 s, e, JOIN : c, (_, _, c ) : d, m s, e, c, d, m
Definições locais Solução simples: traduzir como uma aplicação. compile (let x = e 1 in e 2 ) sym = compile ((λx. e 2 ) e 1 ) sym Alternativa com optimização: ver os livros do Henderson e Kogge.
Compilação do operador ponto-fixo compile (fix λf. λx. e) sym = [LDRF (compile e sym ++ [RTN])] onde sym = extend (extend sym f ) x
Execução Constrói uma closure cíclica: s, e, (LDRF c ) : c, d, m a : s, e, c, d, m onde a = next(m) m = m[a (c, a : e)] Nota: a SECD apresentado no livro de Henderson usa duas instruções (DUM/RAP) para construir closures cíclicas.
Mais informações Muita informação e implementações na web... Wikipedia SECD mania: http//skelet.ludost.net/sec A Rational Deconstruction of Landin s SECD Machine, Olivier Danvy, BRICS research report
Exercícios (1) Compilar para a máquina SECD e executar no interpretador: let x = 42 in 2 x (1) λx. λy. ifzero (x y) 1 else 0 (2) let fib = fix λf. λn. ifzero (n 1) 1 (ifzero (n 2) 1 (f (n 1) + f (n 2))) in fib 3 (3)
Exercícios (2) Modificar o interpretador da SECD para reportar o tamanho máximos da pilha.