Skip to content

Commit

Permalink
switch on types!
Browse files Browse the repository at this point in the history
  • Loading branch information
aardappel committed Jun 17, 2023
1 parent 0021bb1 commit b74f00c
Show file tree
Hide file tree
Showing 17 changed files with 328 additions and 76 deletions.
32 changes: 1 addition & 31 deletions dev/TODO.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,29 +21,6 @@
- Easier to refer people who want to know what is planned and/or want to help.
- Right now, searches for keywords hit both code and this TODO.txt.

- switch on type.
see some discussion here: https://github.com/aardappel/lobster/issues/113
tagged unions: https://github.com/aardappel/lobster/discussions/242
What people really seem to want is a guaranteed exhaustive type switch.
We should first add that for objects.
The way that would work is that it would expect all subclasses to be covered by a type,
including subclasses of subclasses.
To make this manageable, we should first implement abstract classes, since that way the
base class doesn't need to feature in the cases.
Actually, Lobster already knows if a base class is never instanced, but it be nice to give
people the option to enforce it.
We would give never-instanced the same power for convenience, with of course the downside
that if anyone ever tries to instance one suddenly all type switches start complaining
about non-exhaustiveness, but I think this is managable.
For sub-subclasses, the presence of the middle type in the cases will make the sub-subclasses
entirely optional, but if present, should of course get precedence.
Typically though these middle types would be marked abstract if intended to be used with
type switches a lot, which would force cases for the sub-subclasses.
Implementation wise this would go through the same logic that currently constructs vtable
entries, but instead results in case-integers instead of function pointers. That way
this is guaranteed fast no matter the number of cases, though we could choose to bypass
the vtable for 3 or less cases or so.

- We should be making extensive use of https://devblogs.microsoft.com/cppblog/introducing-vector-calling-convention/
Especially since we're a closed static build we control, meaning we have no worries of
ABI/calling convention mismatches.
Expand Down Expand Up @@ -253,9 +230,7 @@
- Features to make it easier to use structs ADT style as opposed to inheritance
- ADT is similar to a base class with no members and a fixed set of sub-classes.
- So make that use case into a simpler declaration syntax.
- extend switch to do pattern matching.
- failing that, a switch that can use types as case labels would be a good start,
and simple to do given that we have type indices!
- extend switch to do ADTs.
- Might be better to not piggy-back on struct inheritance.
- It really does a different thing, where with normal inheritance the base class is not expected
to be the max size of all subclasses.
Expand Down Expand Up @@ -1358,8 +1333,6 @@

- Make includes explicitly depend on eachother.

- switch jump table based version.

- We have a check in IdentRef::TypeCheck to avoid escaping lambdas, but this only works
for static ones, not for calls over a function type.

Expand Down Expand Up @@ -1539,8 +1512,6 @@
- if your forget ":" after "else", it is going to try and declare a following identifier as a
function parameter, see FIXME in ParseTrailingFunctionValues

- extend switch with pattern match facility?

- Should consider to allow on-the fly data structures as well.
Writing new_type { field1: val, field2: val } would add such specialization to new_type even
if it doesn't exist yet.
Expand Down Expand Up @@ -2181,7 +2152,6 @@

- make comparators non-associative?

- some form or switch or pattern match facility?
- pattern matching by failing a function into the next

- make bytecode, not intcode.
Expand Down
12 changes: 12 additions & 0 deletions dev/src/disasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,18 @@ const int *DisAsmIns(NativeRegistry &nfr, string &sd, const int *ip, const int *
break;
}

case IL_JUMP_TABLE_DISPATCH: {
auto vtable_idx = *ip++;
auto mini = *ip++;
auto maxi = *ip++;
auto n = maxi - mini + 2;
append(sd, vtable_idx, "/", n - 1, " [ ");
while (n--)
append(sd, *ip++, " ");
sd += "]";
break;
}

case IL_FUNSTART: {
auto fidx = *ip++;
sd += (fidx >= 0 ? bcf->functions()->Get(fidx)->name()->string_view() : "__dummy");
Expand Down
71 changes: 52 additions & 19 deletions dev/src/lobster/codegen.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ struct CodeGen {
vector<const Node *> node_context;
int keepvars = 0;
int runtime_checks;
vector<int> vtables;
vector<int> vtables; // -1 = uninit, -2 and lower is case idx, positive is code offset.
vector<ILOP> tstack;
size_t tstack_max = 0;
int dummyfun = -1;
Expand Down Expand Up @@ -312,6 +312,10 @@ struct CodeGen {
if (de.sf) {
vtables[udt->vtable_start + i] =
de.sf->subbytecodestart ? de.sf->subbytecodestart : dummyfun;
assert(!de.is_switch_dispatch);
} else if (de.case_index >= 0) {
vtables[udt->vtable_start + i] = -de.case_index - 2;
assert(de.is_switch_dispatch);
}
}
}
Expand Down Expand Up @@ -1599,7 +1603,12 @@ void Break::Generate(CodeGen &cg, size_t retval) const {
void Switch::Generate(CodeGen &cg, size_t retval) const {
cg.Gen(value, 1);
cg.TakeTemp(1, false);
// See if we should do a jump table version.
// See if we do a type dispatch (always a jump table).
if (value->exptype->t == V_CLASS) {
GenerateTypeDispatch(cg, retval);
return;
}
// See if we should do an integer jump table version.
if (GenerateJumpTable(cg, retval))
return;
// Do slow default implementation for sparse integers, expressions and strings.
Expand Down Expand Up @@ -1672,20 +1681,22 @@ void Switch::Generate(CodeGen &cg, size_t retval) const {
bs.Exit(cg);
}

pair<IntConstant *, IntConstant *> get_range(Node *c) {
auto start = c;
auto end = c;
if (auto r = Is<Range>(c)) {
start = r->start;
end = r->end;
}
return { Is<IntConstant>(start), Is<IntConstant>(end) };
};


bool Switch::GenerateJumpTable(CodeGen &cg, size_t retval) const {
if (value->exptype->t != V_INT)
return false;
int64_t mini = INT64_MAX / 2, maxi = INT64_MIN / 2;
int64_t num = 0;
auto get_range = [&](Node *c) -> pair<IntConstant *, IntConstant *> {
auto start = c;
auto end = c;
if (auto r = Is<Range>(c)) {
start = r->start;
end = r->end;
}
return { Is<IntConstant>(start), Is<IntConstant>(end) };
};
for (auto n : cases->children) {
auto cas = AssertIs<Case>(n);
for (auto c : cas->pattern->children) {
Expand All @@ -1712,19 +1723,28 @@ bool Switch::GenerateJumpTable(CodeGen &cg, size_t retval) const {
cg.EmitOp(IL_JUMP_TABLE);
cg.Emit((int)mini);
cg.Emit((int)maxi);
GenerateJumpTableMain(cg, retval, (int)range, (int)mini);
return true;
}

void Switch::GenerateJumpTableMain(CodeGen & cg, size_t retval, int range, int mini) const {
auto table_start = cg.Pos();
for (int i = 0; i < (int)range + 1; i++) cg.Emit(-1);
for (int i = 0; i < range + 1; i++) cg.Emit(-1);
vector<int> exitswitch;
int default_pos = -1;
CodeGen::BlockStack bs(cg.tstack);
for (auto n : cases->children) {
for (auto [i, n] : enumerate(cases->children)) {
bs.Start();
auto cas = AssertIs<Case>(n);
for (auto c : cas->pattern->children) {
auto [istart, iend] = get_range(c);
assert(istart && iend);
for (auto i = istart->integer; i <= iend->integer; i++) {
cg.code[table_start + (int)i - (int)mini] = cg.Pos();
if (value->exptype->t == V_CLASS) {
cg.code[table_start + (int)i] = cg.Pos();
} else {
auto [istart, iend] = get_range(c);
assert(istart && iend);
for (auto i = istart->integer; i <= iend->integer; i++) {
cg.code[table_start + (int)i - mini] = cg.Pos();
}
}
}
if (cas->pattern->children.empty()) default_pos = cg.Pos();
Expand All @@ -1741,12 +1761,25 @@ bool Switch::GenerateJumpTable(CodeGen &cg, size_t retval) const {
cg.EmitOp(IL_JUMP_TABLE_END);
cg.SetLabels(exitswitch);
if (default_pos < 0) default_pos = cg.Pos();
for (int i = 0; i < (int)range + 1; i++) {
for (int i = 0; i < range + 1; i++) {
if (cg.code[table_start + i] == -1)
cg.code[table_start + i] = default_pos;
}
bs.Exit(cg);
return true;
}

void Switch::GenerateTypeDispatch(CodeGen &cg, size_t retval) const {
auto dispatch_udt = value->exptype->udt;
auto &dt = dispatch_udt->dispatch_table[vtable_idx];
assert(dt.is_dispatch_root && dt.is_switch_dispatch &&
dt.subudts_size == dispatch_udt->subudts.size());
(void)dt;
cg.EmitOp(IL_JUMP_TABLE_DISPATCH);
cg.Emit(vtable_idx);
cg.Emit(0);
int range = (int)cases->children.size();
cg.Emit(range - 1);
GenerateJumpTableMain(cg, retval, range, 0);
}

void Case::Generate(CodeGen &/*cg*/, size_t /*retval*/) const {
Expand Down
4 changes: 3 additions & 1 deletion dev/src/lobster/idents.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,9 @@ struct GenericTypeVariable {
};

struct DispatchEntry {
SubFunction *sf = nullptr;
SubFunction *sf = nullptr; // if !is_switch_dispatch
int case_index = -1; // if is_switch_dispatch
bool is_switch_dispatch = false;
bool is_dispatch_root = false;
// Shared return type if root of dispatch.
TypeRef returntype = nullptr;
Expand Down
1 change: 1 addition & 0 deletions dev/src/lobster/il.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ enum MathOp {

#define ILVARARGNAMES \
F(JUMP_TABLE, ILUNKNOWN, 1, 0) \
F(JUMP_TABLE_DISPATCH, ILUNKNOWN, 1, 0) \
F(FUNSTART, ILUNKNOWN, 0, 0)

#define ILJUMPNAMES1 \
Expand Down
7 changes: 6 additions & 1 deletion dev/src/lobster/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,12 @@ BINARY_NODE_T(While, "while", false, Node, condition, Block, wbody, RETURNSMETHO
BINARY_NODE_T(For, "for", false, Node, iter, Block, fbody, )
ZERO_NODE(ForLoopElem, "for loop element", false, )
ZERO_NODE(ForLoopCounter, "for loop counter", false, )
BINARY_NODE_T(Switch, "switch", false, Node, value, List, cases, RETURNSMETHOD bool GenerateJumpTable(CodeGen &cg, size_t retval) const;)
BINARY_NODE_T(Switch, "switch", false, Node, value, List, cases, \
int vtable_idx = -1; \
RETURNSMETHOD \
bool GenerateJumpTable(CodeGen &cg, size_t retval) const; \
void GenerateTypeDispatch(CodeGen &cg, size_t retval) const; \
void GenerateJumpTableMain(CodeGen &cg, size_t retval, int range, int mini) const;)
BINARY_NODE_T(Case, "case", false, List, pattern, Node, cbody, )
BINARY_NODE(Range, "range", false, start, end, )
ZERO_NODE(Break, "break", false, RETURNSMETHOD)
Expand Down
18 changes: 14 additions & 4 deletions dev/src/lobster/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -1397,10 +1397,20 @@ struct Parser {
} else {
Expect(T_CASE);
for (;;) {
auto f = ParseUnary();
if (lex.token == T_DOTDOT) {
lex.Next();
f = new Range(lex, f, ParseUnary());
Node *f = nullptr;
if (lex.token == T_IDENT) {
auto udt = st.LookupSpecialization(lex.sattr);
if (udt) {
lex.Next();
f = new UDTRef(lex, udt);
}
}
if (!f) {
f = ParseUnary();
if (lex.token == T_DOTDOT) {
lex.Next();
f = new Range(lex, f, ParseUnary());
}
}
pattern->Add(f);
if (lex.token == T_COLON) break;
Expand Down
9 changes: 9 additions & 0 deletions dev/src/lobster/tonative.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ inline int ParseOpAndGetArity(int opc, const int *&ip, int &regso) {
arity = int(ip - ips);
break;
}
case IL_JUMP_TABLE_DISPATCH: {
ip++; // vtable_idx
auto mini = *ip++;
auto maxi = *ip++;
auto n = maxi - mini + 2;
ip += n;
arity = int(ip - ips);
break;
}
case IL_FUNSTART: {
ip++; // function idx.
ip++; // max regs.
Expand Down
Loading

0 comments on commit b74f00c

Please sign in to comment.