From 6852981f48f55b245fae6229c2930c3d09d9e45e Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Tue, 6 May 2025 13:44:10 -0700 Subject: README --- chapter22/README.md | 471 ++++----------------- .../com/seaofnodes/simple/type/TypeConAryI.java | 2 +- .../java/com/seaofnodes/simple/Chapter22Test.java | 22 +- 3 files changed, 109 insertions(+), 386 deletions(-) (limited to 'chapter22') diff --git a/chapter22/README.md b/chapter22/README.md index 0fdd9d4a..4cae3ee7 100644 --- a/chapter22/README.md +++ b/chapter22/README.md @@ -1,427 +1,130 @@ -# Chapter 22: Strings and I/O - -It's high time for Strings and printing! In this chapter we look at what it -takes to make a nice user-mode string. We are implementing strings in the base -Simple language (except for perhaps some minor Parser shortcuts). This means -we'll need some canonical representation and a library of string functions. +# Chapter 22: A Hello, World! +It's high time for strings and printing! In this chapter we look at what it +takes to make a basic string - not really a full fledged String class, that will +wait for another chapter. You can also read [this chapter](https://github.com/SeaOfNodes/Simple/tree/linear-chapter21) in a linear Git revision history on the [linear](https://github.com/SeaOfNodes/Simple/tree/linear) branch and [compare](https://github.com/SeaOfNodes/Simple/compare/linear-chapter20...linear-chapter21) it to the previous chapter. +## Nested Types and Static Fields -## FFI and I/O Calls - -Calling external / FFI functions. -No linker support for getting the correct signature; name `write` has to be singular and unique. - -`{ i32 fd, u8[] buf, i32 len -> i32 } write = "C";` - - - -## Importing name spaces - -Ponder a `struct sys{}` default import struct. - -Includes `sys.io{}` struct with I/O calls. - -Includes `sys.libc{}` with default libc calls. - - -## Unifying Structs and Arrays - -The idea is that structs have fields indexed by names, and arrays have fields -indexed by numbers, and these ideas are compatible with each other. This idea -has been used by the C language for a long time - following a normal struct C -allows an array and can index it with the usual array syntax. - -```java -struct str { - int hash; // A cache of the strings hash code - u32 #; // The array length - u8 []; // Array of bytes -}; - -str X = "abc"; -print(X.hash); // Read the hash field -X[0]=='a'; // Read array element 0 -X#; // Length of the array - -``` - -Here, the field named `#` holds the array length and the field named `[]` is -the actual array, which follows the other fields directly in memory. -Here is another example for variable length array: - -```java -struct vecInt { - u32 size; // Actual number of elements - u32 #; // The array length or capacity - int []; // Array of integers. -}; -``` - -The obvious limit here is that the array cannot be directly resized; it is -literally in the same memory block as the leading fields. Such a `vecInt` can -be *re-allocated* to a new larger `vecInt`, but the original `vecInt` will be -reclaimed - you cannot use the same `vecInt` reference forever. - -Any number of named fields are allowed: - -```java -struct Class { - str className; - str professor; - int time; - int credits; - Class prerequisite; - u16 #; // Limit of 65535 students per class - Student []; -}; -``` - -Or self-reference: - -```java -struct NTree { - Some val; - u8 #; // Length is 0-255 - NTree []; // More NTrees -}; -``` - -The syntax rules are: -- the last field is named `[]` and can be of any type. -- the second-to-last field is named `#` and must be some unsigned integer type - representing the maximum array length. -- may require a leading space in front of ` []` - - - -### Constructors and Arrays - -The allocation includes the array length, and can include a constructor: -`new NTree{val=17;}[2];` - -The constructor is optional, the array length is not. - -Optional function for initializing arrays, allows for write-once final arrays: -`new u8[buffer#,{ u32 idx -> buffer[idx].toUpperCase() }]` - - - -### Changing array max length - -An issue for large numbers of range-checked small arrays is that reserving a -full 4 bytes for array length can substantially increase the array overhead. -Simple allows for arrays to have smaller max lengths, although these are -treated as a different type than arrays with different max lengths. - -```java -struct x0 { u8 #; u8[]; }; // Array limited to 0-255 length -struct x1 { u16 #; u8[]; }; // Array limited to 0-65535 length -struct x2 { u32 #; u8[]; }; // -struct x3 { u64 #; u8[]; }; // - -x0 x = "abc"; // 4 bytes total: [3, 'a', 'b', 'c'] -x1 y = "def"; // 6 bytes total: [3, 0 , 'd', 'e', 'f', 0,]; 1 pad byte to acheieve 2-byte alignment -x = y; // ERROR, "x1 is not x0", no auto-widening -x += y; // OK, 7 bytes total: [6, 'a', 'b', 'c', 'd', 'e', 'f'] -y += x; // OK, 8 bytes total: [6, 0 , 'd', 'e', 'f', 'a', 'b', 'c'] - -x += "VeryLongString.... ....255_chars"; // Runtime RangeCheck - -// Explicit conversion to widen string -y = "" + x; -``` - - -## Methods and Final Fields and Classes - -Methods are simply function values (final fields) declared inside a struct that -take an implicit `this` argument. - -Final field declared in the original struct definition are *values* and cannot -change; they are the same for all structs. They get moved out of the struct's -memory footprint and into a *class* - a collection of values from the final -fields. Classes are just plain namespaces and have no concrete implementation; -they are not reified. +Simple now supports nested types - this is a name-space only change, so no new +semantics - just the ability to nest type definitions. ```java -struct vecInt { - // Since "add" is declared with `val` in the original struct definition, - // it is a final field and is moved out of here to the class. - - // Add an element to a vector, possibly returning a new larger vector. - val add = { int e -> - if( size >= # ) return copy(# ? #*2 : 1).add(e); - [size++] = e; - return this; +struct Outer { + struct Inner { + int in; + int pi = 3.14; }; - // Copy 'this' to a new larger size - val copy = { int sz2 -> - val v2 = new vecInt[sz2]; - for( int i=0; i i32 } write = "C";` -## A String Implementation +Any final variable assigned as "C" will be considered defined externally. The +syntax of this might change slightly to avoid ambiguity with assign the same +"C" string. -Default strings have names like "str", "cstr", "ustr" or such. +There is no linker support for getting the correct signature or namespace; the +name `write` has to be unique when linking. -Always unicode8 with explicit u8 manipulations. Unicode "characters" will come -as function calls over the base implementation, probably in another string class. -## Default string +## Auto-Import and the "sys." namespace -Efficiently growable via concantenation. Supports a variety of IO/printf functions. -The underlying array can be shared mutable if you ask for this. -The defaults will always be private mutable and read-only shared. +The `sys` import will eventually include libc bindings, and any default library +code including printing support, and probably collections. As of this chapter +it includes minimal libc bindings and an easy print: ```java -struct str { - - // Add a string to the string, possibly growing - val add = { str str -> - val self = this; - for( int i=0; i - if( len >= # ) return copy(# ? #*2 : 1).add(e); - [size++] = e; - return this; +// top-level default import +struct sys { + // libc bindings + /** https://www.man7.org/linux/man-pages/man2/.2.html */ + struct libc { + // fd buf len -> len + { i32 i64 i32 -> i32 } write = "C"; + // addr len prot flags fd off -> void* + { i64 i64 i32 i32 i32 i32 -> i64 } mmap = "C"; + // mmap flags + i32 PROT_EXEC = 4; + i32 PROT_READ = 1; + i32 PROT_WRITE= 2; + i32 PROT_NONE = 0; + i32 MAP_PRIVATE = 2; + i32 MAP_ANON = 32; }; - - // Copy 'this' to a new larger size - val copy = { int sz2 -> - val v2 = new str[sz2]; - for( int i=0; i + i64 ptr = str; // cast array base to i64 + i32 len = str#; // cast length to signed + return sys.libc.write(1,ptr,len); + }; }; - - val print { -> sys.libc.write(1/*stdout*/,[]/*array base as argument to C*/,len); return this; } - - u32 len; // In-use size - u32 #; // Max length is 4Gig, although variants can request smaller lengths - u8 []; // Character data -}; -``` - - -### A Final String - -Intended to be a **immutable** string, possibly used as a base for a **secure** -string. This needs to be created all at once, and probably requires a -defensive copy. - -```java -struct sstr { - // Possible static call syntax; no references to "this" - val make { str str -> new sstr[str#,str.at] }; - - // Array contents are immutable. - // Still figuring out good syntax for this. - u8 #; - u8 [~]; // Middlin '~'? Makes primitive array contents *immutable* and requires constructor syntax -}; - -// Construction requires a function that produces the array contents. -// Here we are defining a defensive copy over `buf`. -sstr safe = new sstr[buf.len,{ int idx -> buf[idx]; }]; -sstr safe = sstr.make("abc"); // Defensive copy is made -``` - - -### A Extendable String (aka StringBuilder or StringBuffer) - -This API is intended to be a drop-in for a `StringBuilder` style, and takes an -extra indirection with each usage. - -```java -struct xstr { - str !_str; // The extra indirection is here - val add = { str str -> _str = str.add2(str); }; - val write = { int fd -> _str.write(fd); }; -}; -``` - -### A Unicode String - -This API is intended to support unicode character manipulation over a plain `str` - -```java -struct ustr { - // Assuming idx is at the start of a character, return the character - // as a 32b integer. Returns junk integer if not at a character start. - // For all ASCII strings this will amount to a simple byte load. - // For bytes with high order bits set, this may read 1-4 characters. - val at = { int idx -> ... }; -}; -// Unicode character iterator -struct ucharator { -// Return successive unicode character codes -}; -``` - -### A "C" string - -A zero-terminated string, but the length has to be available directly - -```java -struct cstr { - u8 #; // length including the trailing zero - u8[]; // always ends in a zero byte; suitable for direct passing to C string functions }; ``` -### A "german string": a very short string that can exist in a register +The `sys` import itself is Just Another `struct` like any other; it has fields +and types declared internall. Note that these assignments are all final and in +the struct declaration, hence these are all *static* fields. -Length in memory is 1 byte (3 bits?) of length, then 0-7 chars; max 8 chars. -Fits in a 64 bit register. -```java -struct gstr { // aka germanString, or blend string and register "streg" - u3 #; // Size is 0-7 bytes??? - u8 []; // Up to 7 bytes -} -// Using inlined object (see below). All these strings are short and are -// represented as packed integers in a 64b register. -*gstr prize1 = "gold"; -*gstr prize2 = "silver"; -*gstr prize3 = "bronze"; +## Casting a Pointer to an `i64` -``` +You can now cast a pointer to an i64, although not the other way around. +`i64 ptr = str; // cast array base to i64` -### Main becomes `val main = { str[] args -> ...}` +For arrays, this cast is to the array *base* skipping the `#` length field, and +is suitable for passing to external "C" calls. For structs, this is just the +struct base. -## Structs can be inlined +## Arrays of Constants and Constant Arrays -Structs are normally by-reference, but can be converted to by-value by using -the `*` operator, pronounced "contents of" (this name might be changed to -e.g. `inline`). +The Simple type system now supports the notion of a "constant array" - an array +of fixed constants, stored in the ELF file in the RODATA section. We only +support strings at the parser level: "Hello, World!" makes a `u8[13]` array +containing ASCII bytes. -This is a name-space change only, you cannot take the address of the internal -struct without entirely consuming its lifetime. No "escaping". +A constant version of a non-constant array can be assigned using the `u8[~]` +syntax. ```java -// 2 64-bit words, no other overheads -struct Complex { f64 x,y; val len = { ->Math.sqrt(x*x+y*y); }; }; - -// Current Simple rules: always by-reference -struct ByRef { - Complex c; // 4-byte pointer to a Complex -}; -print(new ByRef.c.y); // Lookup requires 1 extra memory load from ref to c - -// New behavior: by value (inlining a struct) -// The '*' syntax indicator can be something else, e.g. a keyword "inline" -// I am pronouncing '*' as "contents of" -struct ByValue { - *Complex c; // c is inlined, full 16 bytes into ByValue +// Make a mutable array of ints +int N=4; +i32[] is = new i32[N]; +// Mutate the array +for( int i=0; i + int sum = 0; + for( int i=0; i ... }; // Passes a complex by value -math.sin(ref.*c); // Requires de-reference to pass by value -math.sin(val. c); // Pass by value - +return sum(is); ``` - - -## RoadMap for other chapters - -`&&` and `||` -Drop `int` to `i32`. -Drop `TypeMemPtr` to 4 bytes; using `mmap` to make heap in low 4G. -Add optimistic pass SCCP to `opto` -Default parser for-loop construction, or partial peel. - `for( init; test; next ) body` -becomes: -```java -{ init; // Normal scope entry to bound lifetime of index variable -if( !test ) { // Zero-trip count test - do { - body; - next; - } while( test ); // Exit test at loop bottom by default -} }; -``` +Note that this affects the deep contents of the array, and not the array +variable itself. diff --git a/chapter22/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java b/chapter22/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java index 2a5900c7..f888a93d 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java @@ -9,7 +9,7 @@ public class TypeConAryI extends TypeConAry { private static final TypeConAryI I123 = make(new int[]{1,2,3}); public static void gather(ArrayList ts) { ts.add(I123); } @Override public String str() { return "[i32]"; } - @Override public int alignment() { return 3; } + @Override public int alignment() { return 2; } @Override boolean eq(Type t) { return Arrays.equals(_ary,((TypeConAryI)t)._ary); } @Override int hash() { return Arrays.hashCode(_ary); } } diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Chapter22Test.java b/chapter22/src/test/java/com/seaofnodes/simple/Chapter22Test.java index 3c228a5b..eddba242 100644 --- a/chapter22/src/test/java/com/seaofnodes/simple/Chapter22Test.java +++ b/chapter22/src/test/java/com/seaofnodes/simple/Chapter22Test.java @@ -32,7 +32,7 @@ public class Chapter22Test { } - @Test public void testJig1() throws IOException { + @Test public void testR5Load() throws IOException { EvalRisc5 R5 = TestRisc5.build("riscv_load", 0, 0, false); int trap = R5.step(100); assertEquals(0,trap); @@ -123,5 +123,25 @@ return cc.cz; assertEquals(0,arm.regs[0]); } + @Test + public void testFinalArray() { + String src = """ +int N=4; +i32[] !is = new i32[N]; +for( int i=0; i + int sum=0; + for( int i=0; i