WIP on new algebraic language, going to change directions again.

master
James T. Martin 2023-07-28 14:09:23 -07:00
parent 1863d420b6
commit 40f88918ef
Signed by: james
GPG Key ID: D6FB2F9892F9B225
9 changed files with 1089 additions and 308 deletions

View File

@ -6,7 +6,7 @@ SHELL = /bin/sh
CFLAGS = -std=c99 -pedantic -Wextra -Os
LDFLAGS = -lc
OBJECTS = asm.o format.o io.o ir.o lex.o lex/indent.o lang.o main.o parse.o x86encode.o
OBJECTS = bytecode.o format.o io.o main.o x86encode.o
.PHONY: passc
passc: .bin $(OBJECTS)

View File

@ -0,0 +1,111 @@
# Intermediate Representations
## Bytecode
### Instructions
Instructions for times:
* `comm : a * b <=> b * a`
* `assocl : a * (b * c) => (a * b) * c`
* `assocr : (a * b) * c => a * (b * c)`
* `mapl (f : a => b) : a * c => b * c`
* `mapr (f : b => c) : a * b => a * c`
* `unitil : a => a * 1`
* `unitir : a => 1 * a`
* `unitel : a * 1 => a`
* `uniter : 1 * a => a`
Instructions for plus:
* `comm : a + b <=> b + a`
* `assocl : a + (b + c) => (a + b) + c`
* `assocr : (a + b) + c => a + (b + c)`
* `mapl (f : a => b) : a + c => b + c`
* `mapr (f : b => c) : a + b => a + c`
* `inl (b : type) : a => a + b`
* `inr (b : type) : b => a + b`
* `out : a + a => a`
Distributivity:
* `distl : a * (b + c) => (a * b) + (a * c)`
* `distr : (a + b) * c => (a * c) + (b * c)`
* `factl : (a * b) + (a * c) => a * (b + c)`
* `factr : (a * c) + (b * c) => (a + b) * c`
Recursion:
* `project: rec r. f(r) -> f(rec r. f(r))`
* `embed: f(rec r. f(r)) -> rec r. f(r)`
`project` and `embed` are no-ops which exist to make type-checking easier
(i.e. isorecursive over equirecursive types).
#### Most instructions are redundant
Most of these instructions are redundant:
* All of the l/r variants can be implemented in terms of each other
using commutativity.
* All of the plus instructions can be implemented in terms of `map`, `in`, and `out`.
* Alternatively, we could have replaced `map` and `out` with a single instruction,
`if (f : a => c) (g : b => c) : a + b => c`.
So "morally", there are only about 10 instructions: `comm`, `assoc`, `map`, `uniti`, `unite`,
`inl`, `inr`, `if`, `dist`, and `fact`.
#### Most instructions are reversible
Inverses of instructions:
* `comm` / `comm`
* `assocl` / `assocr`
* `map f` / `map f*`
* `uniti` / `unite`
* `dist` / `fact`
The only irreversible instructions are `in` and `out`.
#### Instructions are algebraic laws
We have a symmetric monoidal category with coproducts where `*` distributes over `+`.
This isn't quite a distributive symmetric monoidal category, because `*` isn't a product.
Likewise, we *almost* have a distributive lattice (characterized as a meet-semilattice
with binary joins), but `*` isn't guaranteed to be idempotent.
The reversible fragment is a wide dagger symmetric monoidal subcategory.
#### That's really all we need
We simply don't need functions, polymorphism, or `0`.
`0` isn't very interesting when characterized as an initial object
or as the unit for `+`; I find it's only interesting in the context of
second-order polymorphism, as `forall a. a`.
## Finite-state 1-bit cons machine
Instructions:
* `comm`
* `assoc`
* `factor`
* `dist`
* `map`
* `unite`
* `uniti`
* `inl`
* `inr`
Redundant instructions:
* `l`/`r` variants
* `out`
There is a finite number of states, and a state transition table
which determines the next state based on the current state and
a single bit extracted using `dist`.
## Finite-state random-access 1-bit register machine
Instructions:
* `x <- enum(imm, y)`
* `w <- struct(x, y, z)`
* `free x`

489
src/bytecode.c Normal file
View File

@ -0,0 +1,489 @@
#include "bytecode.h"
#include "format.h"
#include "x86encode.h"
#include <sys/mman.h> // TODO: avoid importing for constants
#include <stdlib.h>
// Register convention:
// ax: left side of cons
// dx: right side of cons
// bx: free cell list
// si: map context
// di: unit
// sp: C stack (for FFI)
// bp,cx,r9-r15
//
// Data type representation:
// A+B: ax=tag, bx=value
// A*B: ax=A, bx=B
// 1: ax=1, bx=1
//
// To remove a layer of indirection, we assume that every data type
// is represented by a cons cell, even those that don't need it (i.e. 1).
// This is an extremely naive interpretation of these instructions.
// Ideally, we'd decompile all of these structural rules (especially
// associativity and commutativity) back into variables, and then perform
// register allocation.
void comm(void) {
// swap the left and right side of the cons
x86_inst_xchg_r64_rax(DX);
}
void assocl(void) {
// a, a * b
// b * c, a
// a * c, b
// a * b, c
// xchg a, d
// xchg d, [a]
// xchg d, [a+8]
x86_inst_xchg_r64_rax(DX);
x86_inst_xchg_r64_m64(DX, AX);
x86_inst_xchg_r64_m64_disp8(DX, AX, 8);
}
void assocr(void) {
// a * b, c
// c, a * b
// b, a * c
// a, b * c
// xchg a, d
// xchg a, [d+8]
// xchg a, [d]
x86_inst_xchg_r64_rax(DX);
x86_inst_xchg_r64_m64_disp8(AX, DX, 8);
x86_inst_xchg_r64_m64(AX, DX);
}
void distl(void) {
// a, b + c
// a * b + a * c
// a, (tag, bc)
// tag, (a, bc)
// xchg a, [d]
x86_inst_xchg_r64_m64(AX, DX);
// Awfully convenient how that works out, huh?
}
void distr(void) {
// The intermediate states here are ill-typed, but ultimately everything
// gets shuffled around to the right locations.
// a + b, c
// c + c, a/b
// a/b, c+c
// a * c + b * c
// (tag, ab), c
// (tag, c), ab
// ab, (tag, c)
// tag, (ab, c)
// xchg d, [a+8]
// xchg d, [a]
// xchg a, d
x86_inst_xchg_r64_m64_disp8(DX, AX, 8);
x86_inst_xchg_r64_m64(DX, AX);
x86_inst_xchg_r64_rax(DX);
}
void factl(void) {
// a * b + a * c:
// a * (b + c)
// tag, (a, bc)
// a, (tag, bc)
// xchg a, [d]
x86_inst_xchg_r64_m64(AX, DX);
}
void factr(void) {
// a * c + b * c
// (a + b) * c
// tag, (ab, c)
// ab, (tag, c)
// (tag, c), ab
// (tag, ab), c
// xchg a, [d]
// xchg a, d
// xchg [a+8], d
x86_inst_xchg_r64_m64(AX, DX);
x86_inst_xchg_r64_rax(DX);
x86_inst_xchg_r64_m64_disp8(DX, AX, 8);
}
static void allocate_cons(void) {
// a, b free=(_, next)
// _, b free=(a, next)
// _, next free=(a, b)
// _, (a, b) free=next
x86_inst_mov_m64_r64(BX, AX);
x86_inst_xchg_r64_m64_disp8(DX, BX, 8);
x86_inst_xchg_r64_r64(DX, BX);
// a, b free=(_, next)
// (_, next), b free=a
// (_, b), next free=a
// (_, b), a free=next
// (a, b), _ free=next
}
static void free_cons(void) {
// _, (a, b) free=next
// a, (_, b) free=next
// a, (_, next) free=b
// a, b free=(_, next)
x86_inst_mov_r64_m64(AX, DX);
x86_inst_xchg_r64_m64_disp8(BX, DX, 8);
x86_inst_xchg_r64_r64(DX, BX);
}
void mapl_begin(void) {
x86_inst_push_r64(DX);
x86_inst_xchg_r64_rax(DX);
free_cons();
}
void mapl_end(void) {
allocate_cons();
x86_inst_xchg_r64_rax(DX);
x86_inst_pop_r64(DX);
}
void mapr_begin(void) {
x86_inst_push_r64(AX);
free_cons();
}
void mapr_end(void) {
allocate_cons();
x86_inst_pop_r64(AX);
}
void unitil(void) {
allocate_cons();
x86_inst_xchg_r64_rax(DX);
x86_inst_mov_r64_r64(DX, DI);
}
void unitir(void) {
allocate_cons();
x86_inst_mov_r64_r64(AX, DI);
}
void unitel(void) {
x86_inst_xchg_r64_rax(DX);
free_cons();
}
void uniter(void) {
free_cons();
}
void comm_plus(void) {
x86_inst_xor_al_imm8(1);
}
static void inst_jump(symbol sym) {
int32_t disp = symbol_offset(sym, X86_JMP_DISP8_SIZE);
if (disp >= INT8_MIN && disp <= INT8_MAX) {
x86_inst_jmp_disp8(disp);
} else {
x86_inst_jmp_disp32_op();
relocate_pc32(sym);
}
// TODO: support 64-bit jumps?
}
static void inst_jump_if_zero(symbol sym) {
int32_t disp = symbol_offset(sym, X86_JZ_DISP8_SIZE);
if (disp >= INT8_MIN && disp <= INT8_MAX) {
x86_inst_jz_disp8(disp);
} else {
x86_inst_jz_disp32_op();
relocate_pc32(sym);
}
}
static void inst_jump_if_not_zero(symbol sym) {
int32_t disp = symbol_offset(sym, X86_JNZ_DISP8_SIZE);
if (disp >= INT8_MIN && disp <= INT8_MAX) {
x86_inst_jnz_disp8(disp);
} else {
x86_inst_jnz_disp32_op();
relocate_pc32(sym);
}
}
// NOTE:
// This is a really stupid implementation of assoc.
// However, it might not be worth optimizing compared to
// eliminating assoc entirely when possible.
void assocl_plus(void) {
// a + (b + c)
// (a + b) + c
// tag, a/(tag, b/c)
symbol when_bc = new_symbol();
symbol end_if = new_symbol();
x86_inst_test_r8_r8(AX, AX);
inst_jump_if_not_zero(when_bc);
///// A
// 0, a
allocate_cons();
// 0, (0, a)
inst_jump(end_if);
/// BC
define_executable_symbol(when_bc);
// 1, (tag, b/c)
symbol when_c = new_symbol();
x86_inst_test_m8_imm8(DX, 1);
inst_jump_if_not_zero(when_c);
/// B
// 1, (0, b)
x86_inst_xchg_r64_m64(AX, DX);
// 0, (1, b)
inst_jump(end_if);
/// C
define_executable_symbol(when_c);
// 1, (1, c)
free_cons();
// 1, c
define_executable_symbol(end_if);
// tag, (tag, a/b)/c
}
// This is the same as assocl, but with `jump_if_not_zero`
// replaced with `jump_if_zero`.
void assocr_plus(void) {
// (a + b) + c
// a + (b + c)
// tag, (tag, a/b)/c
symbol when_ab = new_symbol();
symbol end_if = new_symbol();
x86_inst_test_r8_r8(AX, AX);
inst_jump_if_zero(when_ab);
/// C
// 1, c
allocate_cons();
// 1, (1, c)
inst_jump(end_if);
/// AB
define_executable_symbol(when_ab);
// 0, (tag, a/b)
symbol when_a = new_symbol();
x86_inst_test_m8_imm8(AX, 1);
inst_jump_if_zero(when_a);
/// B
// 0, (1, b)
x86_inst_xchg_r64_m64(AX, DX);
// 1, (0, b)
inst_jump(end_if);
/// A
define_executable_symbol(when_a);
// 1, (1, a)
free_cons();
define_executable_symbol(end_if);
// tag, a/(tag, b/c)
}
#define MAX_BRANCHES 64
static symbol branches[MAX_BRANCHES];
static size_t branchi = 0;
static symbol new_branch() {
if (branchi == MAX_BRANCHES) {
fprintf(stderr, "exeeded maximum number of plus maps\n");
exit(1);
}
symbol* branch = &branches[branchi++];
*branch = new_symbol();
return *branch;
}
void mapl_plus_begin(void) {
symbol end_branch = new_branch();
x86_inst_test_r8_r8(AX, AX);
inst_jump_if_not_zero(end_branch);
free_cons();
}
void mapl_plus_end(void) {
allocate_cons();
x86_zero(AX);
define_executable_symbol(branches[--branchi]);
}
void mapr_plus_begin(void) {
symbol end_branch = new_branch();
x86_inst_test_r8_r8(AX, AX);
inst_jump_if_zero(end_branch);
free_cons();
}
void mapr_plus_end(void) {
allocate_cons();
x86_inst_mov_r64_imm(AX, 1);
define_executable_symbol(branches[--branchi]);
}
void inl(void) {
allocate_cons();
x86_zero(AX);
}
void inr(void) {
allocate_cons();
x86_inst_mov_r64_imm(AX, 1);
}
void out(void) {
// a + a
// a
free_cons();
}
static void inst_load(reg dest, symbol sym) {
x86_inst_lea_r64_rip_disp32_op(dest);
relocate_pc32(sym);
}
static symbol one_symbol;
static symbol loop_point;
static symbol exit_point;
void quit(void) {
inst_jump(exit_point);
}
static void print_unary(void) {
// System calls will mangle AX and DX.
x86_inst_mov_r64_r64(R12, AX);
x86_inst_mov_r64_r64(R14, DX);
symbol loop_point = new_symbol();
symbol exit_point = new_symbol();
define_executable_symbol(loop_point);
// Print `1` until we stop hitting rights.
x86_inst_test_r8_r8(R12, R12);
inst_jump_if_zero(exit_point);
x86_inst_mov_r64_m64(R12, R14);
x86_inst_mov_r64_m64_disp8(R14, R14, 8);
x86_inst_mov_r64_imm(AX, 1); // sys_write
x86_inst_mov_r64_imm(DI, 1); // stdout
inst_load(SI, one_symbol);
x86_inst_mov_r64_imm(DX, 1);
x86_inst_syscall();
inst_jump(loop_point);
define_executable_symbol(exit_point);
}
static void exit_syscall(void) {
x86_inst_mov_r64_imm(AX, 60); // sys_exit
x86_zero(DI);
x86_inst_syscall();
}
#define MEMORY_SIZE 0x100000
static void initialize_free_list(void) {
// allocate 1 MiB with a syscall, because it's easier than
// adding an ELF RW segment for the moment.
x86_inst_mov_r64_imm(AX, 9);
x86_inst_mov_r64_imm(DI, (uint64_t) -1);
x86_inst_mov_r64_imm(SI, MEMORY_SIZE);
x86_inst_mov_r64_imm(DX, 0x3 /* PROT_READ | PROT_WRITE */);
x86_inst_mov_r64_imm(R10, 0x8022 /* MAP_PRIVATE | MAP_POPULATE | MAP_ANONYMOUS */);
x86_inst_mov_r64_imm(R8, (uint64_t) -1);
x86_zero(R9);
x86_inst_syscall();
// The beginning of the free list.
x86_inst_mov_r64_r64(BX, AX);
x86_inst_mov_r64_imm(CX, MEMORY_SIZE / 2);
x86_inst_lea_r64_m64_disp8(DX, AX, 8);
symbol exit_point = new_symbol();
symbol loop_point = new_symbol();
define_executable_symbol(loop_point);
x86_inst_test_r64_r64(CX, CX);
inst_jump_if_zero(exit_point);
x86_inst_mov_m64_r64_disp8(AX, DX, 8);
x86_inst_mov_r64_r64(AX, DX);
x86_inst_add_r64_imm8(DX, 16);
x86_inst_sub_r64_imm8(CX, 16);
inst_jump(loop_point);
define_executable_symbol(exit_point);
}
symbol init_bytecode(void) {
one_symbol = new_symbol();
define_executable_symbol(one_symbol);
append_u8((uint8_t) '1');
exit_point = new_symbol();
define_executable_symbol(exit_point);
print_unary();
exit_syscall();
symbol entry_point = new_symbol();
define_executable_symbol(entry_point);
initialize_free_list();
// Self-referential unit value.
//x86_inst_lea_r64_m64_disp8(DI, SP, -16);
x86_inst_mov_r64_r64(DI, SP);
x86_inst_sub_r64_imm8(DI, 16);
x86_inst_push_r64(DI);
x86_inst_push_r64(DI);
// Initial state is a unit in the left.
// (Right states will be loop states.)
x86_inst_mov_r64_r64(AX, DI);
x86_inst_mov_r64_r64(DX, DI);
inl();
loop_point = new_symbol();
define_executable_symbol(loop_point);
return entry_point;
}
void finish_bytecode(void) {
inst_jump(loop_point);
}

57
src/bytecode.h Normal file
View File

@ -0,0 +1,57 @@
#ifndef _BYTECODE_H
#define _BYTECODE_H
#include "format.h"
/// a * b <=> b * a
void comm(void);
/// a * (b * c) => (a * b) * c
void assocl(void);
/// (a * b) * c => a * (b * c)
void assocr(void);
/// a * (b + c) => (a * b) + (a * c)
void distl(void);
/// (a + b) * c => (a * c) + (b * c)
void distr(void);
/// (a * b) + (a * c) => a * (b + c)
void factl(void);
/// (a * c) + (b * c) => (a + b) * c
void factr(void);
/// (a => b) => (a * c => b * c)
void mapl_begin(void);
void mapl_end(void);
/// (b => c) => (a * b => a * c)
void mapr_begin(void);
void mapr_end(void);
/// a => a * 1
void unitil(void);
/// a => 1 * a
void unitir(void);
/// a * 1 => a
void unitel(void);
/// 1 * a => a
void uniter(void);
/// a + b <=> b + a
void comm_plus(void);
/// a + (b + c) => (a + b) + c
void assocl_plus(void);
/// (a + b) + c => a + (b + c)
void assocr_plus(void);
/// (a => b) => (a + c => b + c)
void mapl_plus_begin(void);
void mapl_plus_end(void);
/// (b => c) => (a + b => a + c)
void mapr_plus_begin(void);
void mapr_plus_end(void);
/// a => a + b
void inl(void);
/// b => a + b
void inr(void);
/// a + a => a
void out(void);
void quit(void);
symbol init_bytecode(void);
void finish_bytecode(void);
#endif

View File

@ -1,241 +0,0 @@
#include "flat_register.h"
#include "../format.h"
#include "../x86encode.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct fr_label_info {
symbol symbol;
uint8_t argc;
fr_type types[MAX_ARGS];
} fr_label_info;
typedef struct fr_var_info {
fr_type type;
_Bool literal;
uint64_t value;
} fr_var_info;
static size_t fr_labelc = 0;
static fr_label_info fr_labels[MAX_LABELS];
static size_t fr_varc = 0;
static fr_var_info fr_vars[MAX_VARS];
static uint32_t fr_var_offset = 0;
static fr_var fr_push(fr_type type) {
fr_var_info* info = &fr_vars[fr_varc];
info->type = type;
info->literal = false;
info->value = fr_var_offset;
fr_var_offset += type.size;
return fr_varc++;
}
fr_label fr_declare(size_t typec, fr_type* types) {
fr_label_info* info = &fr_labels[fr_labelc];
info->symbol = new_symbol();
info->argc = typec;
memcpy(info->types, types, typec * sizeof(fr_type));
return fr_labelc++;
}
void fr_define(fr_label l, fr_var* vars) {
fr_label_info label = fr_labels[l];
fr_varc = 0;
fr_var_offset = 0;
for (uint8_t i = 0; i < label.argc; i++) {
vars[i] = fr_push(label.types[i]);
}
}
fr_var fr_lit(fr_type type, uint64_t val) {
fr_var_info* info = &fr_vars[fr_varc];
info->type = type;
info->literal = true;
info->value = val;
return fr_varc++;
}
// TODO: register allocation instead of infinitely-growing stack
void fr_load_reg(reg reg, fr_var v) {
fr_var_info var = fr_vars[v];
if (var.literal && var.type.size == 8) {
x86_inst_mov_r64_imm(reg, var.value);
} else if (var.type.size == 8 || var.type.tag == FR_BOX) {
x86_inst_mov_r64_m64_disp(reg, BP, var.value);
} else if (var.literal && var.type.size == 1) {
x86_inst_mov_r8_m8_disp(reg, BP, var.value);
} else if (var.type.size == 1) {
x86_inst_mov_r8_imm8(reg, var.value);
} else if (var.type.tag == FR_REF) {
x86_inst_lea_r64_m64_disp(reg, BP, var.value);
} else {
fprintf(stderr, "unsupported variable size, for now\n");
exit(1);
}
}
void fr_store_reg(reg reg, fr_var v) {
fr_var_info var = fr_vars[v];
if (var.type.size == 8 || var.type.tag == FR_BOX) {
x86_inst_mov_m64_r64_disp(reg, BP, var.value);
} else if (var.type.size == 1) {
x86_inst_mov_m8_r8_disp(reg, BP, var.value);
} else {
fprintf(stderr, "unsupported variable size, for now\n");
exit(1);
}
}
fr_var fr_index(fr_var box, fr_var index) {
// TODO: optimized constant multiplies
// TODO: fuse into indirect addressing modes
fr_load_reg(AX, index);
x86_inst_mov_r64_imm32(DX, fr_vars[index].type.size);
x86_inst_mul_r64(DX);
fr_type type = {
.tag = FR_BOX,
.size = 8,
};
fr_var var = fr_push(type);
fr_store_reg(AX, var);
return var;
}
void fr_set(fr_var box, fr_var index, fr_var ref) {
// TODO: use fused addressing modes, optimize for small types
// TODO: use `lea`, optimize for constant index
fr_load_reg(AX, index);
fr_load_reg(DI, box);
fr_load_reg(SI, ref);
x86_inst_mov_r64_imm(CX, fr_vars[box].type.size);
x86_inst_mul_r64(CX);
x86_inst_add_r64_r64(DI, AX);
x86_inst_rep_movsb();
}
fr_var fr_load(fr_var box, fr_type type, uint32_t offset) {
// TODO: optimized copy for small (i.e. almost all) types
fr_var dest = fr_push(type);
fr_load_reg(SI, box);
x86_inst_mov_r64_imm(CX, type.size);
x86_inst_lea_r64_m64_disp(DI, BP, offset);
x86_inst_rep_movsb();
return dest;
}
fr_var fr_struct(uint32_t memberc, fr_var* members) {
uint32_t size = 0;
for (uint32_t i = 0; i < memberc; i++) {
size += fr_vars[members[i]].type.size;
}
fr_type type = {
.tag = FR_REF,
.size = size,
};
fr_var var = fr_push(type);
uint32_t offset = fr_vars[var].value;
// TODO: optimized copy for small (i.e. almost all) types
for (uint32_t i = 0; i < memberc; i++) {
fr_var_info member = fr_vars[members[i]];
fr_load_reg(AX, members[i]);
if (member.type.size == 1) {
x86_inst_mov_m8_r8_disp(AX, BP, offset);
offset += 1;
} else if (member.type.size == 8 || member.type.tag == FR_BOX) {
x86_inst_mov_m64_r64_disp(AX, BP, offset);
offset += 8;
} else {
x86_inst_lea_r64_m64_disp(SI, BP, member.value);
x86_inst_lea_r64_m64_disp(DI, BP, offset);
x86_inst_mov_r64_imm(CX, member.type.size);
x86_inst_rep_movsb();
}
}
return var;
}
static void fr_prepare_jump(size_t argc, fr_var* args) {
// TODO: avoid unnecessary copying
fr_var scratch = fr_struct(argc, args);
fr_load_reg(SI, scratch);
x86_inst_mov_r64_r64(DI, BP);
x86_inst_mov_r64_imm(CX, fr_vars[scratch].type.size);
x86_inst_rep_movsb();
}
void inst_jump(symbol sym) {
int32_t disp = symbol_offset(sym, X86_JMP_DISP8_SIZE);
if (disp >= INT8_MIN && disp <= INT8_MAX) {
x86_inst_jmp_disp8(disp);
} else {
x86_inst_jmp_disp32_op();
relocate_pc32(sym);
}
// TODO: support 64-bit jumps?
}
void inst_jump_if_zero(symbol sym) {
int32_t disp = symbol_offset(sym, X86_JZ_DISP8_SIZE);
if (disp >= INT8_MIN && disp <= INT8_MAX) {
x86_inst_jz_disp8(disp);
} else {
x86_inst_jz_disp32_op();
relocate_pc32(sym);
}
}
void inst_jump_if_not_zero(symbol sym) {
int32_t disp = symbol_offset(sym, X86_JNZ_DISP8_SIZE);
if (disp >= INT8_MIN && disp <= INT8_MAX) {
x86_inst_jnz_disp8(disp);
} else {
x86_inst_jnz_disp32_op();
relocate_pc32(sym);
}
}
void fr_if(fr_cond cond, fr_var x, fr_var y, fr_label ifl, fr_label elsel, size_t argc, fr_var* args) {
fr_load_reg(BX, x);
fr_load_reg(DX, y);
fr_prepare_jump(argc, args);
x86_inst_cmp_r64_r64(BX, DX);
symbol ifs = fr_labels[ifl].symbol;
symbol elses = fr_labels[elsel].symbol;
switch (cond) {
case FR_EQ:
inst_jump_if_zero(ifs);
inst_jump(elses);
break;
case FR_NE:
inst_jump_if_not_zero(ifs);
inst_jump(elses);
break;
}
}
void fr_switch(fr_var index, size_t labelc, fr_label* labels, size_t argc, fr_var* args) {
// TODO:
/*
symbol table = new_symbol();
fr_load_reg(CX, index);
x86_inst_lea_r64_ip_disp32(AX, X86_JMP_M64_SIZE);
x86_inst_add_r64_r64(AX, CX);
x86_inst_jmp_m64(
*/
fprintf(stderr, "jump tables not yet implemented\n");
exit(1);
}
void fr_jump(fr_label label, size_t argc, fr_var* args) {
fr_prepare_jump(argc, args);
inst_jump(fr_labels[label].symbol);
}

View File

@ -1,43 +0,0 @@
#ifndef FLAT_REGISTER_H
#define FLAT_REGISTER_H
/// An IR with flat (i.e. non-recursive) syntax and types.
/// Registers only contain bitvectors. Memory layout (size and offsets) are explicit.
#include <stddef.h>
#include <stdint.h>
typedef size_t fr_label;
typedef size_t fr_var;
#define MAX_LABELS 4096
#define MAX_VARS 4096
#define MAX_ARGS 32
enum fr_type_tag {
FR_UINT,
FR_BOX,
FR_REF,
};
typedef struct fr_type {
enum fr_type_tag tag;
uint32_t size;
} fr_type;
typedef enum fr_cond {
FR_EQ,
FR_NE,
} fr_cond;
fr_label fr_declare(size_t typec, fr_type* types);
void fr_define(fr_label label, fr_var* vars);
fr_var fr_lit(fr_type type, uint64_t val);
fr_var fr_index(fr_var box, fr_var index);
void fr_set(fr_var box, fr_var index, fr_var ref);
fr_var fr_load(fr_var box, fr_type type, uint32_t offset);
fr_var fr_struct(uint32_t memberc, fr_var* members);
void fr_if(fr_cond cond, fr_var x, fr_var y, fr_label ifl, fr_label elsel, size_t argc, fr_var* args);
void fr_switch(fr_var index, size_t labelc, fr_label* labels, size_t argc, fr_var* args);
void fr_jump(fr_label label, size_t argc, fr_var* args);
#endif

View File

@ -3,25 +3,310 @@
#include <stdio.h>
#include <stdint.h>
#include "bytecode.h"
#include "format.h"
#include "io.h"
#include "ir.h"
#include "parse.h"
#define ELF_HEADER_SIZE 0xb0
// a + (b + (c + d))
// (a + b) + (c + d)
// (b + a) + (c + d)
// b + (a + (c + d))
//
void transition_right(void) {
assocl_plus();
mapl_plus_begin();
out();
inr();
mapl_plus_end();
assocr_plus();
}
void transition_left(void) {
out();
inl();
}
void jump_from_to(size_t from, size_t to) {
if (from < to) {
mapl_plus_begin();
inl();
for(; from <= to; to--) {
inr();
}
mapl_plus_end();
mapr_plus_begin();
inr();
mapr_plus_end();
out();
} else if (to > from) {
for (size_t i = 0; i < from - to; i++) {
mapr_plus_end();
mapl_plus_begin();
inl();
mapl_plus_end();
}
}
}
void transition_into(void) {
assocl_plus();
mapl_plus_begin();
mapl_plus_begin();
inl();
mapl_plus_end();
out();
inr();
mapl_plus_end();
assocr_plus();
}
void transition_while(void) {
assocl_plus();
mapl_plus_begin();
mapr_plus_begin();
inr();
mapr_plus_end();
out();
mapl_plus_end();
assocr_plus();
}
void inc(void) {
inr();
factl();
}
void new_nat(void) {
// ctx
unitil(); // ctx * 1
inl(); // ctx * 1 + ctx * 1
factl(); // ctx * (1 + 1)
}
void swap(void) {
assocr();
mapr_begin();
comm();
mapr_end();
assocl();
}
static void select_var(size_t var) {
// (... * a) * (b * (c * ...))
for (size_t i = 0; i < var; i++) {
assocr();
// ((... * a) * b) * (c * ...)
}
comm();
// (c * ...) * ((... * a) * b)
assocl();
// ((c * ...) * (... * a)) * b
}
static void unselect_var(size_t var) {
assocr();
comm();
for (size_t i = 0; i < var; i++) {
assocl();
}
}
static void case_on(size_t var) {
select_var(var);
distr();
mapl_plus_begin(); {
unselect_var(var);
} mapl_plus_end();
mapr_plus_begin(); {
unselect_var(var);
} mapr_plus_end();
}
static void snipe(size_t var) {
select_var(var);
unitel();
comm();
for (size_t i = 0; i < var; i++) {
assocl();
}
}
symbol compile(void) {
symbol entry_point = new_symbol();
define_executable_symbol(entry_point);
var argc, argv, env;
init_ir(&argc, &argv, &env);
parse();
var a = lit(52);
var b = lit(10);
var exit_code = sub(a, b);
var sys_exit = lit(60);
var args[2] = { sys_exit, exit_code };
syscall(2, args);
symbol entry_point = init_bytecode();
// This is the program we're trying to execute:
//
// fib n = fib_acc n 0 1
// fib_acc 0 a b = a
// fib_acc (S n) a b = fib_acc n b (a + b)
//
// Looks simple, right? Well, things are a bit more complicated than that.
//
// 1. In `fib_acc 0`, we implicitly drop the value of `b`. Because we do not have
// weakening, we will have to free `b` explicitly here.
//
// fib_acc 0 a 0 = a
// fib_acc 0 a (S b) = fib_acc 0 a b
//
// 2. In `fib_acc (S n)`, we use `b` twice. We do not have contraction, so we must
// explicitly duplicate it, or implicitly duplicate it when we consume `b`.
//
// 3. We do not have addition as a built-in; we will need to define it ourselves.
// Moreover, we do not have functions, so it must be fused into the definition
// of fib_acc.
//
// -- We will duplicate `b` into the first argument (the new `a`)
// -- while adding it to the second argument (`a`, which will become the new `b`).
// fib_acc (S n) a b = fib_acc_plus n 0 a b
// fib_acc_plus n a b' 0 = fib_acc n a b'
// fib_acc_plus n a b' (S b) = fib_acc_plus n (S a) (S b') b
//
// 4. We'll have to do a lot of tedious work shuffling variables around.
// We don't even have implicit associativity, much less commutativity!
//
// We have this hierarchy of states:
//
// 1. start(1)
// 2. fib(n)
// 3. fib_acc(n, a, b)
// 4. fib_acc(0, a, b)
// 5. fib_acc_0(a b)
//
// States:
// * start(1)
// * fib(n)
// * fib_acc(n, a, b)
// * fib_acc_Z(1, (a, b))
// * fib_acc_Z_free(a, b)
// * fib_acc_Z_done
// * fib_acc_S
// * fib_acc_S_copy
// * fib_acc_S_copy_done
// * fib_acc_S_copy_S
// State 0: starting state
mapl_plus_begin();
// Initialize with integer (5).
inl();
inr();
inr();
inr();
inr();
inr();
mapl_plus_end();
transition_right();
mapr_plus_begin();
// State 1: fib(n);
mapl_plus_begin();
// a = 0
new_nat();
// b = 1
new_nat();
inc();
mapl_plus_end();
transition_right();
mapr_plus_begin();
// State 2: fib_acc(n, a, b)
mapl_plus_begin();
// if n=1, we return the accumulated value
assocr();
distl();
mapl_plus_end();
transition_right();
mapr_plus_begin();
mapl_plus_begin();
// State 3.1.1: fib_acc_Z(1, (a, b))
mapl_plus_begin();
uniter();
// (a, b)
mapl_plus_end();
transition_into();
mapr_plus_begin();
// State 3.1.2.1: fib_acc_Z_free(a, b)
mapl_plus_begin();
// n * (1 + n)
distr();
mapl_plus_end();
transition_while();
// State 3.1.2.2: fib_acc_Z_done
mapr_plus_begin();
uniter();
quit();
mapr_plus_end();
mapr_plus_end();
mapl_plus_end();
mapr_plus_begin();
// State 4: fib_acc_S
mapl_plus_begin();
assocl();
new_nat();
swap();
new_nat();
swap();
mapl_plus_end();
transition_into();
mapr_plus_begin();
mapl_plus_begin();
// State 5.1: fib_acc_S_copy(n, a, b1, b2, b)
mapl_plus_begin();
distl();
mapl_plus_end();
transition_into();
mapr_plus_begin();
mapl_plus_begin();
// State 5.2.1: fib_acc_S_copy_done(n, a, b, b, 1)
uniter();
// TODO:
mapl_plus_end();
mapr_plus_begin();
// State 5.2.2: fib_acc_S_copy_S(n, a, b1, b2, b)
mapr_plus_end();
mapr_plus_end();
mapl_plus_end();
mapr_plus_end();
mapr_plus_end();
mapr_plus_end();
mapr_plus_end();
mapr_plus_end();
// State 1: fib(n)
assocl_plus();
mapl_plus_begin();
// switch to state 2
out();
inr();
mapl_plus_end();
assocr_plus();
mapr_plus_begin();
// State 2: fib_acc(n, a, b)
mapl_plus_begin();
// State 2.1: transition to state 3
mapl_plus_begin();
mapr_plus_end();
mapr_plus_end();
finish_bytecode();
return entry_point;
}

View File

@ -30,6 +30,25 @@ static void x86_opt_rexr(reg reg) {
}
}
static void x86_opt_rexb(reg reg) {
if (reg >= R8) {
append_u8(REX | REX_B);
}
}
static void x86_opt_rexrb(reg r, reg b) {
uint8_t rex = REX;
if (r >= R8) {
rex |= REX_R;
}
if (b >= R8) {
rex |= REX_B;
}
if (rex != REX) {
append_u8(rex);
}
}
static void x86_rexwr(reg reg) {
uint8_t rex = REX | REX_W;
if (reg >= R8) rex |= REX_R;
@ -70,29 +89,29 @@ static void x86_modxm(uint8_t ext, reg b) {
}
static void x86_enc_opr(uint8_t op, reg reg) {
x86_opt_rexr(reg);
x86_opt_rexb(reg);
append_u8(op + REG(reg));
}
static void x86_enc_rexw_opr(uint8_t op, reg reg) {
x86_rexwr(reg);
x86_rexwb(reg);
append_u8(op + REG(reg));
}
static void x86_enc_opr_imm32(uint8_t op, reg reg, uint32_t imm) {
x86_opt_rexr(reg);
x86_opt_rexb(reg);
append_u8(op + REG(reg));
append_u32(imm);
}
static void x86_enc_rexw_opr_imm32(uint8_t op, reg reg, uint32_t imm) {
x86_rexwr(reg);
x86_rexwb(reg);
append_u8(op + REG(reg));
append_u32(imm);
}
static void x86_enc_rexw_opr_imm64(uint8_t op, reg reg, uint64_t imm) {
x86_rexwr(reg);
x86_rexwb(reg);
append_u8(op + REG(reg));
append_u64(imm);
}
@ -155,6 +174,12 @@ static void x86_enc_rexw_modxm_imm(uint8_t op, uint8_t ext, reg m, uint32_t imm)
}
}
static void x86_enc_modrr(uint8_t op, reg r, reg b) {
x86_opt_rexrb(r, b);
append_u8(op);
x86_modrr(r, b);
}
static void x86_enc_disp8(uint8_t op, int8_t disp) {
uint8_t buf[2] = { op, (uint8_t) disp };
append_data(2, buf);
@ -203,19 +228,57 @@ void x86_inst_mov_r64_m64_disp(reg dest, reg src, int32_t disp) {
}
void x86_inst_mov_m64_r64(reg dest, reg src) {
x86_enc_rexw_modrm(0x8a, src, dest);
x86_enc_rexw_modrm(0x89, src, dest);
}
void x86_inst_mov_m64_r64_disp8(reg dest, reg src, int8_t disp) {
x86_enc_rexw_modrm8(0x8a, src, dest, disp);
x86_enc_rexw_modrm8(0x89, src, dest, disp);
}
void x86_inst_mov_m64_r64_disp32(reg dest, reg src, int32_t disp) {
x86_enc_rexw_modrm32(0x8a, src, dest, disp);
x86_enc_rexw_modrm32(0x89, src, dest, disp);
}
void x86_inst_mov_m64_r64_disp(reg dest, reg src, int32_t disp) {
x86_enc_rexw_modrmd(0x8a, src, dest, disp);
x86_enc_rexw_modrmd(0x89, src, dest, disp);
}
void x86_inst_mov_r32_r32(reg dest, reg src) {
x86_enc_modrr(0x8b, dest, src);
}
void x86_inst_xchg_r64_rax(reg src) {
x86_enc_rexw_opr(0x90, src);
}
void x86_inst_xchg_r64_r64(reg dest, reg src) {
if (src == AX) {
x86_inst_xchg_r64_rax(dest);
} else if (dest == AX) {
x86_inst_xchg_r64_rax(src);
} else {
x86_enc_rexw_modrr(0x87, dest, src);
}
}
void x86_inst_xchg_r64_m64(reg dest, reg src) {
x86_enc_rexw_modrm(0x87, dest, src);
}
void x86_inst_xchg_r64_m64_disp8(reg dest, reg src, int8_t disp) {
x86_enc_rexw_modrm8(0x87, dest, src, disp);
}
void x86_inst_xchg_r64_m64_disp32(reg dest, reg src, int32_t disp) {
x86_enc_rexw_modrm32(0x87, dest, src, disp);
}
void x86_inst_xchg_r64_m64_disp(reg dest, reg src, int32_t disp) {
if (disp == 0) {
x86_inst_xchg_r64_r64(dest, src);
} else {
x86_enc_rexw_modrmd(0x87, dest, src, disp);
}
}
void x86_inst_push_r64(reg reg) {
@ -230,6 +293,16 @@ void x86_inst_test_r64_r64(reg r1, reg r2) {
x86_enc_rexw_modrr(0x85, r1, r2);
}
void x86_inst_test_r8_r8(reg r1, reg r2) {
x86_enc_modrr(0x84, r1, r2);
}
void x86_inst_test_m8_imm8(reg r, uint8_t imm) {
x86_opt_rexr(r);
x86_enc_opr(0xf6, r);
append_u8(imm);
}
void x86_inst_jmp_disp8(int8_t disp) {
x86_enc_disp8(0xeb, disp);
}
@ -297,6 +370,25 @@ void x86_inst_jz_disp32_op(void) {
append_u8(0x84);
}
void x86_inst_xor_r32_r32(reg dest, reg src) {
x86_enc_modrr(0x31, dest, src);
}
void x86_inst_xor_m8_imm8(reg dest, uint8_t imm) {
x86_opt_rexr(dest);
x86_enc_opr(0x80, dest);
append_u8(imm);
}
void x86_inst_xor_al_imm8(uint8_t imm) {
append_u8(0x34);
append_u8(imm);
}
void x86_zero(reg dest) {
x86_inst_xor_r32_r32(dest, dest);
}
// TODO: special instructions for AX
void x86_inst_sub_r64_imm8(reg dest, int8_t imm) {
x86_enc_rexw_modxm_imm8(0x83, 5, dest, (uint8_t) imm);
@ -323,6 +415,20 @@ void x86_inst_add_r64_r64(reg dest, reg src) {
x86_enc_rexw_modrr(0x01, src, dest);
}
void x86_inst_lea_r64_m64_disp8(reg dest, reg src, int8_t disp) {
x86_enc_rexw_modrm8(0x8d, dest, src, disp);
}
void x86_inst_lea_r64_rip_disp32_op(reg dest) {
x86_rexwr(dest);
append_u8(0x8d);
x86_modrm(dest, BP);
}
void x86_inst_lea_r64_rip_disp32(reg dest, int32_t disp) {
x86_enc_rexw_modrm32(0x8d, dest, BP, disp);
}
void x86_inst_syscall(void) {
const uint8_t buf[2] = { 0x0f, 0x05 };
append_data(2, buf);

View File

@ -48,11 +48,21 @@ void x86_inst_mov_r8_m8_disp8(reg dest, reg src, int8_t disp);
void x86_inst_mov_r8_m8_disp32(reg dest, reg src, int32_t disp);
void x86_inst_mov_r8_m8_disp(reg dest, reg src, int32_t disp);
void x86_inst_mov_r32_r32(reg dest, reg src);
void x86_inst_xchg_r64_rax(reg src);
void x86_inst_xchg_r64_r64(reg dest, reg src);
void x86_inst_xchg_r64_m64(reg dest, reg src);
void x86_inst_xchg_r64_m64_disp8(reg dest, reg src, int8_t disp);
void x86_inst_xchg_r64_m64_disp32(reg dest, reg src, int32_t disp);
void x86_inst_xchg_r64_m64_disp(reg dest, reg src, int32_t disp);
void x86_inst_push_r64(reg reg);
void x86_inst_pop_r64(reg reg);
void x86_inst_test_r8_r8(reg r1, reg r2);
void x86_inst_test_r64_r64(reg r1, reg r2);
void x86_inst_test_m8_imm8(reg r, uint8_t imm);
void x86_inst_cmp_r64_r64(reg r1, reg r2);
@ -77,6 +87,11 @@ void x86_inst_jz_disp32(int32_t disp);
void x86_inst_jz_disp(int32_t disp);
void x86_inst_jz_disp32_op(void);
void x86_inst_xor_r32_r32(reg dest, reg src);
void x86_inst_xor_al_imm8(uint8_t imm);
void x86_inst_xor_m8_imm8(reg dest, uint8_t imm);
void x86_zero(reg dest);
void x86_inst_sub_r64_imm8(reg dest, int8_t imm);
void x86_inst_sub_r64_imm32(reg dest, int32_t imm);
void x86_inst_sub_r64_imm(reg dest, int32_t imm);
@ -87,9 +102,11 @@ void x86_inst_add_r64_r64(reg dest, reg src);
void x86_inst_mul_r64(reg src);
void x86_inst_lea_r64_m64_disp8(reg dest, reg src, int32_t disp);
void x86_inst_lea_r64_m64_disp8(reg dest, reg src, int8_t disp);
void x86_inst_lea_r64_m64_disp32(reg dest, reg src, int32_t disp);
void x86_inst_lea_r64_m64_disp(reg dest, reg src, int32_t disp);
void x86_inst_lea_r64_rip_disp32_op(reg dest);
void x86_inst_lea_r64_rip_disp32(reg dest, int32_t disp);
void x86_inst_rep_movsb(void);