allocators and type aliases

This commit is contained in:
nora 2023-08-03 13:22:35 +02:00
parent 60c743d656
commit f05e5520f3
16 changed files with 283 additions and 121 deletions

View file

@ -51,6 +51,7 @@ module.exports = {
// This lint is horrible with noisy false positives every time there are typescript errors. // This lint is horrible with noisy false positives every time there are typescript errors.
"@typescript-eslint/no-unsafe-return": "off", "@typescript-eslint/no-unsafe-return": "off",
"@typescript-eslint/no-unsafe-assignment": "off", "@typescript-eslint/no-unsafe-assignment": "off",
"eslint@typescript-eslint/no-unsafe-argument": "off",
// Useful extra lints that are not on by default: // Useful extra lints that are not on by default:
"@typescript-eslint/explicit-module-boundary-types": "warn", "@typescript-eslint/explicit-module-boundary-types": "warn",

View file

@ -144,10 +144,20 @@ export type FunctionArg<P extends Phase> = {
export type TypeDef<P extends Phase> = { export type TypeDef<P extends Phase> = {
name: string; name: string;
fields: FieldDef<P>[]; type: TypeDefKind<P>;
ty?: TyStruct; ty?: TyStruct;
}; };
export type TypeDefKind<P extends Phase> =
| {
kind: "struct";
fields: FieldDef<P>[];
}
| {
kind: "alias";
type: Type<P>;
};
export type FieldDef<P extends Phase> = { export type FieldDef<P extends Phase> = {
name: Ident; name: Ident;
type: Type<P>; type: Type<P>;
@ -624,15 +634,29 @@ export function superFoldItem<From extends Phase, To extends Phase>(
}; };
} }
case "type": { case "type": {
const fields = item.node.fields.map(({ name, type }) => ({ const typeKind = item.node.type;
name, let type: TypeDefKind<To>;
type: folder.type(type), switch (typeKind.kind) {
})); case "struct": {
const fields = typeKind.fields.map(({ name, type }) => ({
name,
type: folder.type(type),
}));
type = { kind: "struct", fields };
break;
}
case "alias": {
type = {
kind: "alias",
type: folder.type(typeKind.type),
};
}
}
return { return {
...item, ...item,
kind: "type", kind: "type",
node: { name: item.node.name, fields }, node: { name: item.node.name, type },
}; };
} }
case "import": { case "import": {

View file

@ -36,8 +36,8 @@ const WASM_PAGE = 65536;
const DUMMY_IDX = 9999999; const DUMMY_IDX = 9999999;
const ALLOCATE_ITEM: string[] = ["std", "rt", "allocateItem"]; const ALLOCATE_ITEM: string[] = ["std", "rt", "alloc", "allocateItem"];
const DEALLOCATE_ITEM: string[] = ["std", "rt", "deallocateItem"]; const DEALLOCATE_ITEM: string[] = ["std", "rt", "alloc", "deallocateItem"];
type RelocationKind = type RelocationKind =
| { | {

View file

@ -79,7 +79,11 @@ export function parseArgs(hardcodedInput: string): Options {
} }
input = fs.readFileSync(filename, { encoding: "utf-8" }); input = fs.readFileSync(filename, { encoding: "utf-8" });
packageName = path.basename(filename, ".nil"); if (filename.endsWith(".mod.nil")) {
packageName = path.basename(filename, ".mod.nil");
} else {
packageName = path.basename(filename, ".nil");
}
const debugArg = process.argv.find((arg) => arg.startsWith("--debug=")); const debugArg = process.argv.find((arg) => arg.startsWith("--debug="));
if (debugArg !== undefined) { if (debugArg !== undefined) {

View file

@ -13,9 +13,14 @@ import { GlobalContext, parseArgs } from "./context";
import { loadCrate } from "./loader"; import { loadCrate } from "./loader";
const INPUT = ` const INPUT = `
type A = { a: Int }; type A = struct { a: Int };
type What = What;
type Uwu = (Int, Int);
function main() = ( function main() = (
let a: What = 0;
uwu(); uwu();
); );
@ -28,7 +33,7 @@ function uwu() = (
/*-1*/ /*-1*/
); );
type B = { type B = struct {
a: (Int, Int, Int, Int, Int), a: (Int, Int, Int, Int, Int),
}; };
@ -59,7 +64,9 @@ function main() {
() => { () => {
const start = Date.now(); const start = Date.now();
gcx.crateLoader(gcx, "std", Span.startOfFile(file)); if (packageName !== "std") {
gcx.crateLoader(gcx, "std", Span.startOfFile(file));
}
const tokens = tokenize(file); const tokens = tokenize(file);
if (debug.has("tokens")) { if (debug.has("tokens")) {

View file

@ -13,6 +13,7 @@ export type DatalessToken =
| "extern" | "extern"
| "mod" | "mod"
| "global" | "global"
| "struct"
| "(" | "("
| ")" | ")"
| "{" | "{"
@ -323,6 +324,7 @@ const KEYOWRDS: DatalessToken[] = [
"extern", "extern",
"mod", "mod",
"global", "global",
"struct",
]; ];
const KEYWORD_SET = new Set<string>(KEYOWRDS); const KEYWORD_SET = new Set<string>(KEYOWRDS);

View file

@ -31,6 +31,7 @@ import {
ItemId, ItemId,
GlobalItem, GlobalItem,
StructLiteralField, StructLiteralField,
TypeDefKind,
} from "./ast"; } from "./ast";
import { CompilerError, LoadedFile, Span } from "./error"; import { CompilerError, LoadedFile, Span } from "./error";
import { import {
@ -107,32 +108,46 @@ function parseItem(t: State): [State, Item<Parsed>] {
let name; let name;
[t, name] = expectNext<TokenIdent>(t, "identifier"); [t, name] = expectNext<TokenIdent>(t, "identifier");
[t] = expectNext(t, "="); [t] = expectNext(t, "=");
[t] = expectNext(t, "{");
let fields; let type: TypeDefKind<Parsed>;
[t, fields] = parseCommaSeparatedList<FieldDef<Parsed>>(t, "}", (t) => {
let name; let struct;
[t, name] = expectNext<TokenIdent>(t, "identifier"); [t, struct] = eat(t, "struct");
[t] = expectNext(t, ":"); if (struct) {
let type; [t] = expectNext(t, "{");
[t, type] = parseType(t);
return [ let fields;
t, [t, fields] = parseCommaSeparatedList<FieldDef<Parsed>>(t, "}", (t) => {
{ let name;
name: { [t, name] = expectNext<TokenIdent>(t, "identifier");
name: name.ident, [t] = expectNext(t, ":");
span: name.span, let type;
[t, type] = parseType(t);
return [
t,
{
name: {
name: name.ident,
span: name.span,
},
type,
}, },
type, ];
}, });
];
}); type = { kind: "struct", fields };
} else {
let aliased: Type<Parsed>;
[t, aliased] = parseType(t);
type = { kind: "alias", type: aliased };
}
[t] = expectNext(t, ";"); [t] = expectNext(t, ";");
const def: TypeDef<Parsed> = { const def: TypeDef<Parsed> = {
name: name.ident, name: name.ident,
fields, type,
}; };
return [ return [

View file

@ -63,14 +63,22 @@ function printFunction(func: FunctionDef<AnyPhase>): string {
} }
function printTypeDef(type: TypeDef<AnyPhase>): string { function printTypeDef(type: TypeDef<AnyPhase>): string {
const fields = type.fields.map( switch (type.type.kind) {
({ name, type }) => `${ind(1)}${name.name}: ${printType(type)},`, case "struct": {
); const { fields } = type.type;
const fieldPart = const fieldStr = fields.map(
type.fields.length === 0 ? "{}" : `{\n${fields.join("\n")}\n}`; ({ name, type }) => `${ind(1)}${name.name}: ${printType(type)},`,
);
const fieldPart =
fields.length === 0 ? "{}" : `{\n${fieldStr.join("\n")}\n}`;
return `type ${type.name} = ${fieldPart};`; return `type ${type.name} = ${fieldPart};`;
}
case "alias": {
return `type ${type.name} = ${printType(type.type.type)}`;
}
}
} }
function printImportDef(def: ImportDef<AnyPhase>): string { function printImportDef(def: ImportDef<AnyPhase>): string {

View file

@ -175,23 +175,29 @@ export function typeck(
return ty; return ty;
} }
case "type": { case "type": {
const ty: Ty = { switch (item.node.type.kind) {
kind: "struct", case "struct": {
name: item.node.name, const ty: Ty = {
fields: [ kind: "struct",
/*dummy*/ name: item.node.name,
], fields: [
}; /*dummy*/
],
};
itemTys.set(item.id, ty); itemTys.set(item.id, ty);
const fields = item.node.fields.map<[string, Ty]>(({ name, type }) => [ const fields = item.node.type.fields.map<[string, Ty]>(
name.name, ({ name, type }) => [name.name, lowerAstTy(type)],
lowerAstTy(type), );
]);
ty.fields = fields; ty.fields = fields;
return ty; return ty;
}
case "alias": {
return lowerAstTy(item.node.type.type);
}
}
} }
case "mod": { case "mod": {
throw new CompilerError( throw new CompilerError(
@ -313,32 +319,51 @@ export function typeck(
}; };
} }
case "type": { case "type": {
const fieldNames = new Set(); switch (item.node.type.kind) {
item.node.fields.forEach(({ name }) => { case "struct": {
if (fieldNames.has(name)) { const fieldNames = new Set();
throw new CompilerError( item.node.type.fields.forEach(({ name }) => {
`type ${item.node.name} has a duplicate field: ${name.name}`, if (fieldNames.has(name)) {
name.span, throw new CompilerError(
); `type ${item.node.name} has a duplicate field: ${name.name}`,
} name.span,
fieldNames.add(name); );
}); }
fieldNames.add(name);
});
const ty = typeOfItem(item.id, item.span) as TyStruct; const ty = typeOfItem(item.id, item.span) as TyStruct;
return { return {
...item, ...item,
node: { node: {
name: item.node.name, name: item.node.name,
fields: item.node.fields.map((field, i) => ({ type: {
name: field.name, kind: "struct",
type: { fields: item.node.type.fields.map((field, i) => ({
...field.type, name: field.name,
ty: ty.fields[i][1], type: {
...field.type,
ty: ty.fields[i][1],
},
})),
},
}, },
})), };
}, }
}; case "alias": {
return {
...item,
node: {
name: item.node.name,
type: {
kind: "alias",
type: item.node.type.type,
},
},
};
}
}
} }
case "mod": { case "mod": {
return { return {

View file

@ -1,44 +0,0 @@
// Start the heap at 1024. In practice this could probably be as low as we want.
// TODO: The compiler should set this global to whatever it has calculated the heap
// start to be. But well, 1024 ought to be enough for now. lol.
global HEAD_PTR: I32 = 1024_I32;
// Every struct has a header of an I32 as a refcount.
// Allocate a new item. We do not deallocate anything yet.
// lol.
function allocateItem(objSize: I32, align: I32): I32 = (
if align < 4_I32 then std.abort("invalid alignment");
// Include the refcount header.
let actualSize = 4_I32 + objSize;
// Let's see whether we can fit the refcount into the align bits.
// I happen to know that everything will always be at least 4 bytes aligned.
let alignedPtr = std.alignUp(HEAD_PTR, align);
let actualObjPtr = if (alignedPtr - HEAD_PTR) > align then (
alignedPtr - 4_I32
) else (
// Take up the next spot.
alignedPtr + align - 4_I32
);
let newHeadPtr = actualObjPtr + actualSize;
if newHeadPtr > __memory_size() then (
// 16 pages, very arbitrary.
let result = __memory_grow(16_I32);
// If allocation failed we get -1. We don't have negative numbers yet, lol.
if result > 4294967295_I32 then (
std.abort("failed to grow memory");
);
);
HEAD_PTR = newHeadPtr;
actualObjPtr
);
function deallocateItem(ptr: I32, objSize: I32) = (
std.println("uwu deawwocate :3");
);

112
std/rt/alloc.nil Normal file
View file

@ -0,0 +1,112 @@
// Start the heap at 1024. In practice this could probably be as low as we want.
// TODO: The compiler should set this global to whatever it has calculated the heap
// start to be. But well, 1024 ought to be enough for now. lol.
global HEAD_PTR: I32 = 1024_I32;
// Every struct has a header of an I32 as a refcount.
// Allocate a new item. We do not deallocate anything yet.
// lol.
function allocateItem(objSize: I32, align: I32): I32 = (
if align < 4_I32 then std.abort("invalid alignment");
// Include the refcount header.
let actualSize = 4_I32 + objSize;
// Let's see whether we can fit the refcount into the align bits.
// I happen to know that everything will always be at least 4 bytes aligned.
let alignedPtr = std.alignUp(HEAD_PTR, align);
let actualObjPtr = if (alignedPtr - HEAD_PTR) > align then (
alignedPtr - 4_I32
) else (
// Take up the next spot.
alignedPtr + align - 4_I32
);
let newHeadPtr = actualObjPtr + actualSize;
if newHeadPtr > __memory_size() then (
// 16 pages, very arbitrary.
let result = __memory_grow(16_I32);
// If allocation failed we get -1. We don't have negative numbers yet, lol.
if result > 4294967295_I32 then (
std.abort("failed to grow memory");
);
);
HEAD_PTR = newHeadPtr;
actualObjPtr
);
function deallocateItem(ptr: I32, objSize: I32) = (
std.println("uwu deawwocate :3");
);
// Port of https://github.com/CCareaga/heap_allocator
//
// MIT License
//
// Copyright (c) 2017 Chris Careaga
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
global HEAP_START: I32 = 2048_I32;
// heap size = start+end+bin_t*BIN_COUNT
// 4+ 4+ 4* 9 = 8+36=42 (round to 64)
global HEAP_REGION_START: I32 = 2112_I32;
// typedef struct node_t {
// uint hole;
// uint size;
// struct node_t* next;
// struct node_t* prev;
// } node_t;
// typedef struct {
// node_t *header;
// } footer_t;
// typedef struct {
// node_t* head;
// } bin_t;
global SIZEOF_NODE: I32 = 16_I32;
global SIZEOF_FOOTER: I32 = 4_I32;
function initHeap() = (
let heap_init_size = 65536_I32 - HEAP_REGION_START;
__i32_store(HEAP_REGION_START, 1_I32); // START.hole =
__i32_store(HEAP_REGION_START + 4_I32, heap_init_size - SIZEOF_NODE - SIZEOF_FOOTER); // START.size =
);
function createFoot(head_node: I32) = (
let foot = getFoot(head_node);
__i32_store(foot, head_node); // foot.header = head_node
);
function getFoot(node: I32): I32 = (
let node_size = __i32_load(node + 4_I32); // node.size
node + SIZEOF_NODE + node_size
);
function addNode(bin: I32, node: I32) = ;
function test() =;

1
std/rt/rt.mod.nil Normal file
View file

@ -0,0 +1 @@
mod alloc;

View file

@ -79,5 +79,5 @@ function abort(message: String) = (
); );
function main() = ( function main() = (
std.rt.allocateItem(100000000_I32, 8_I32); std.rt.alloc.test();
); );

View file

@ -0,0 +1,7 @@
//@check-pass
type A = (Int, Int);
function main() = (
let a: A = (0, 0);
);

View file

@ -1,6 +1,6 @@
//@check-pass //@check-pass
type CustomType = {}; type CustomType = struct {};
function main() = (); function main() = ();

View file

@ -17,7 +17,7 @@
"patterns": [ "patterns": [
{ {
"name": "keyword.control.riverdelta", "name": "keyword.control.riverdelta",
"match": "\\b(function|let|if|then|else|type|loop|break|import|extern|mod|global)\\b" "match": "\\b(function|let|if|then|else|type|loop|break|import|extern|mod|global|struct)\\b"
} }
] ]
}, },